13

我有这堂课:

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.
4

3 回答 3

14

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

于 2013-08-15T20:17:29.833 回答
6
  vector<Foo> vf;
  cout << "Insert a temporary." << endl;
  vf.emplace_back(Foo{});

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

然后使用此临时文件Foovector. 因此,您要求的是“由右值引用构建”。

如果您想简单地构建Foo就地,请尝试:

  vs.emplace_back();

下一个:

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

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

有趣的是,您通过左值引用获得了两个构造。第二个似乎是由调整大小引起的。为什么调整大小会导致您被左值引用而不是右值引用构造,这是一个技巧:如果您的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);
}
于 2013-08-15T20:50:58.410 回答
-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 或 reference-to-reference 传递)。初始 push_back 调用创建容量为 1(和大小为 1)。第二次调用创建一个新的内存块,移动第一个项目,并复制第二个(新添加的)项目。

在性能方面,您不会因为小的重新分配而受到很大的打击。使用了几种不同的常见内存模型(带有 Visual Studio 的内存模型每次必须增长以减少未来对它的需求时,都会以指数方式增长您的容量)。如果你知道你将从 100 个元素开始,你应该在创建向量时保留空间,这样分配只会发生一次,这也将避免在插入新元素时移动现有元素的需要(因为你不会多次超出您的能力)。

于 2013-08-15T20:19:57.157 回答