我有这堂课:
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.
答案 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
构造函数没有标记为noexcept
,std::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个元素开始,你应该在创建向量时保留空间,这样分配只发生一次,这也可以防止在插入新元素时移动现有元素的需要(因为你不会超过你的容量多次)。