std :: vector初始化移动/复制元素的构造函数

时间:2014-07-17 00:26:28

标签: c++ c++11 move-semantics copy-elision

我有这段代码:

#include <iostream>
#include <vector>

using namespace std;

class Foo{
public:
    Foo() noexcept {cout << "ctor" << endl;}
    Foo(const Foo&) noexcept {cout << "copy ctor" << endl;}
    Foo(Foo&&) noexcept {cout << "move ctor" << endl;}

    Foo& operator=(Foo&&) noexcept {cout << "move assn" << endl; return *this;}
    Foo& operator=(const Foo&) noexcept {cout << "copy assn" << endl; return *this;}

    ~Foo() noexcept {cout << "dtor" << endl;}
};


int main()
{   
    Foo foo;

    vector<Foo> v;
    v.push_back(std::move(foo)); 

    // comment the above 2 lines and replace by
    // vector<Foo> v{std::move(foo)}; 
}

输出是我所期望的(用g++ -std=c++11 --no-elide-constructors编译,相同的输出没有标志)

ctor
move ctor
dtor
dtor

现在,而不是使用push_back直接将矢量v初始化为

vector<Foo> v{std::move(foo)};

我不明白为什么我得到输出:

1)(没有--no-elide-constructors

ctor
move ctor
copy ctor
dtor
dtor
dtor

2)(--no-elide-constructors

ctor
move ctor
move ctor
copy ctor
dtor
dtor
dtor
dtor

在第一种情况下,为什么要调用copy ctor?在第二种情况下,当编译器不执行elision时,我完全不知道为什么移动ctor被调用两次。有任何想法吗?

2 个答案:

答案 0 :(得分:8)

vector<Foo> v{std::move(foo)};

在这里,您将调用带有std::initializer_list的向量构造函数。初始化列表仅允许const访问其元素,因此vector必须将每个元素从initializer_list复制到其自己的存储中。这就是调用复制构造函数的原因。

来自§8.5.4/ 5 [dcl.init.list]

  

类型为std::initializer_list<E>的对象是从初始化列表构造的,就好像该实现分配了一个临时数组 N const E 元素,其中{ {1}}是初始值设定项列表中的元素数。


至于使用N的额外移动构造函数调用,几天前在another answer中对此进行了讨论。似乎g ++对我在上面引用的相同部分中标准中显示的-fno-elide-constructors的示例实现采用了非常直观的方法。

同样的例子,当compiled using clang时,不会产生额外的移动构造函数调用。

答案 1 :(得分:3)

容器尝试非常,以确保在发生异常时它们仍然可用。作为其中的一部分,如果您的类的移动构造函数是异常安全的,它们将仅在内部使用std::move。如果不是,(或它无法分辨),它将复制只是为了安全。

正确的移动操作

Foo(Foo&&) noexcept {cout << "move ctor" << endl;}
Foo& operator=(Foo&&) noexcept {cout << "move assn" << endl;}