簡體   English   中英

為什么vector :: push_back和emplace_back調用value_type :: constructor兩次?

[英]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

然后使用該臨時Foovector構造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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM