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

时间:2013-08-15 19:54:22

标签: c++ c++11

我有这堂课:

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

然后我插入一个向量:

Foo foo{};
vf.push_back(foo);

输出结果令人惊讶:

constructed by lvalue reference.
constructed by lvalue reference.

我认为在传递参数时它被复制了,所以我尝试了:

vf.push_back(move(foo));

vf.push_back(forward<Foo>(foo));

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

constructed by rvalue reference.
constructed by lvalue reference.

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


我在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;
}

确切输出:

Insert a temporary.
constructed by rvalue reference.
Insert a variable.
constructed by lvalue reference.
constructed by lvalue reference.

3 个答案:

答案 0 :(得分:14)

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

答案 1 :(得分:6)

  vector<Foo> vf;
  cout << "Insert a temporary." << endl;
  vf.emplace_back(Foo{});

上面发生的是创建了一个临时Foo

然后,此临时用于在Foo内构建vector。因此,“由右值参考构建”就是你所要求的。

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

  vs.emplace_back();

下一步:

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

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

有趣的是,你通过左值引用得到两个构造。第二个似乎是由调整大小引起的。为什么调整大小会导致你被左值引用而不是右值引用构造,这是一个诀窍:如果你的move构造函数没有标记为noexceptstd::vector会回到副本而不是{ {1}}!

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

move

答案 2 :(得分:-3)

std::vector::push_back的常见实现如下:

void push_back(value_type _Val)
{   // insert element at end
    insert_n(size(), 1, _Val);
}

如您所见,输入参数在push_back声明和insert_n声明中都是按值传递(因此将被复制)。因此,复制构造函数被调用两次。

清理语法后:

#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()
{
    vector<Foo> vf;
    cout << "Size = " << vf.size() << endl;
    cout << "Capacity = " << vf.capacity() << endl;

    cout << "Insert a temporary" << endl;
    vf.push_back(Foo()); // this is still very discouraged syntax

    Foo foo;
    cout << "Insert a variable." << endl;
    vf.push_back(foo);

    return 0;
}

您将获得以下输出:

Size = 0
Capacity = 0
Insert a temporary
constructed by rvalue reference.
Insert a variable.
constructed by rvalue reference.
constructed by lvalue reference.
Press any key to continue . . .

在这个例子中,我使用标准版本的std :: vector(通过const-reference或引用引用)。初始push_back调用创建容量为1(大小为1)。第二个调用创建一个新的内存块,移动第一个项目,并复制第二个(新添加的)项目。

就性能而言,您不会因小型重新分配而受到重创。使用了一些不同的常见内存模型(Visual Studio每次都需要以指数方式增加容量,以便在将来减少对它的需求)。如果你知道你将从100个元素开始,你应该在创建向量时保留空间,这样分配只发生一次,这也可以防止在插入新元素时移动现有元素的需要(因为你不会超过你的容量多次)。