[英]Why does vector::push_back and emplace_back call value_type::constructor twice?
我有這個課:
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.
在向量中插入新項目時,向量可能必須分配更多內存以適合那些對象。 發生這種情況時,需要將所有元素復制到新的內存位置。 這將調用復制構造函數。 因此,當您插入元素時,您將獲得該新元素的構造函數以及復制前一個元素時的構造函數。
vector<Foo> vf;
cout << "Insert a temporary." << endl;
vf.emplace_back(Foo{});
上面發生的是創建了一個臨時Foo
。
然后使用該臨時Foo
在vector
構造Foo
。 因此,您需要“通過右值引用構造”。
如果您只想就地構建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);
}
std::vector::push_back
常見實現如下所示:
void push_back(value_type _Val)
{ // insert element at end
insert_n(size(), 1, _Val);
}
如您所見,輸入參數在push_back
聲明和insert_n
聲明中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的一種,每次需要增加以減少對它的需求時,Visual Studio就會成倍地增加容量)。 如果您知道將從100個元素開始,則應在創建矢量時保留空間,以便分配僅發生一次,這也將避免在插入新元素時移動現有元素的需要(由於您不會多次超出您的容量)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.