为什么vector :: push_back和emplace_back两次调用value_type :: constructor?

tex*_*uce 13 c++ c++11

我有这门课:

class Foo {
public:
    Foo() {}
    Foo(const Foo&){cout << "constructed by lvalue reference." <<endl; }
    Foo(Foo&& ) {cout << "constructed by rvalue reference." << endl; }
};
Run Code Online (Sandbox Code Playgroud)

然后我插入一个向量:

Foo foo{};
vf.push_back(foo);
Run Code Online (Sandbox Code Playgroud)

输出令人惊讶:

constructed by lvalue reference.
constructed by lvalue reference.
Run Code Online (Sandbox Code Playgroud)

我假设它在传递参数时被复制,所以我试过:

vf.push_back(move(foo));
Run Code Online (Sandbox Code Playgroud)

vf.push_back(forward<Foo>(foo));
Run Code Online (Sandbox Code Playgroud)

由于移动语义但仍然调用构造函数两次,输出略有不同:

constructed by rvalue reference.
constructed by lvalue reference.
Run Code Online (Sandbox Code Playgroud)

为什么构造函数被调用两次?它会影响多少性能?我怎么能避免这个?


我在Windows Vista上使用mingw-gcc-4.7.1

总例子:

#include <iostream>
#include <vector>

using namespace std;

class Foo {
public:
    Foo() {}
    Foo(const Foo&){cout << "constructed by lvalue reference." <<endl; }
    Foo(Foo&& ) {cout << "constructed by rvalue reference." << endl; }
};


int main(int argc, char **argv, char** envp)
{
    vector<Foo> vf;
    cout << "Insert a temporary." << endl;
    vf.emplace_back(Foo{});

    Foo foo{};
    cout << "Insert a variable." << endl;
    vf.emplace_back(foo);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

确切的输出:

Insert a temporary.
constructed by rvalue reference.
Insert a variable.
constructed by lvalue reference.
constructed by lvalue reference.
Run Code Online (Sandbox Code Playgroud)

小智 14

在向量中插入新项时,向量可能必须分配更多内存以适合这些对象.当发生这种情况时,它需要将其所有元素复制到新的内存位置.那将调用复制构造函数.因此,当您插入元素时,您将获得该元素的构造函数以及复制前一个元素时的构造函数.

  • @ZacHowland`memcpy`?大声笑. (8认同)
  • @ZacHowland:唯一使用`memcpy`用于平凡可复制类型,其中`Foo`在这里是_not_. (4认同)
  • 重新分配存储时,如果可能,实际应移动对象,而不是复制.正在复制该对象,因为移动构造函数未标记为"noexcept". (2认同)

Yak*_*ont 6

  vector<Foo> vf;
  cout << "Insert a temporary." << endl;
  vf.emplace_back(Foo{});
Run Code Online (Sandbox Code Playgroud)

上面发生的Foo是创建临时.

然后,该临时被用来构建一个Foovector.因此,"由右值参考构建"是您所要求的.

如果您希望简单地构建Foo到位,请尝试:

  vs.emplace_back();
Run Code Online (Sandbox Code Playgroud)

下一个:

  Foo foo{};
  cout << "Insert a variable." << endl;
  vf.emplace_back(foo);
Run Code Online (Sandbox Code Playgroud)

在这里你构建一个非临时的foo.然后,您指示std::vector在列表末尾构造一个新元素.

有趣的是,你通过左值引用得到两个构造.第二个似乎是由调整大小引起的.为什么调整大小使你的左值参考construced而不是右值引用,是一招:如果你的move构造函数没有标记noexcept,std::vector倒在副本而不是move!

是一个说明上述原则的实例:

#include <iostream>
#include <vector>

using namespace std;

class Foo {

public:
  Foo() {}
  virtual ~Foo() {}
  Foo(const Foo&){cout << "constructed by lvalue reference." <<endl; }
  Foo(Foo&){cout << "constructed by non-const lvalue reference." <<endl; }
  Foo(Foo&& ) noexcept {cout << "constructed by rvalue reference." << endl; }
};


int main(int argc, char **argv, char** envp)
{
  vector<Foo> vf;
  cout << "Insert a temporary.  One move:" << endl;
  vf.emplace_back(Foo{});
  cout << "Insert a temporary(2).  Two moves:" << endl;
  vf.emplace_back(Foo{});
  cout << "Resize with temporary(3).  Two moves:" << endl;
  vf.resize(10);

  vector<Foo> vf2;
  Foo foo{};
  cout << "Insert a variable.  One copy:" << endl;
  vf2.emplace_back(foo);
  cout << "Insert a variable(2).  One move, one copy:" << endl;
  vf2.emplace_back(foo);
  cout << "Resize with variable(3).  Two moves:" << endl;
  vf2.resize(10);

  vector<Foo> vf3;
  cout << "Insert a nothing.  No copy or move:" << endl;
  vf3.emplace_back();
  cout << "Insert a nothing(2).  One move:" << endl;
  vf3.emplace_back();
  cout << "Resize with nothing(3).  Two moves:" << endl;
  vf3.resize(10);
}
Run Code Online (Sandbox Code Playgroud)