2

我有这段代码:

#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

在第一种情况下,为什么要调用复制 ctor?在第二种情况下,当编译器不执行省略时,我完全不知道为什么调用了 move ctor 两次。有任何想法吗?

4

2 回答 2

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

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

§8.5.4/5 [dcl.init.list]

type 的对象std::initializer_list<E>是从初始化列表构造的,就好像实现分配了一个临时N元素数组 typeconst E,其中N是初始化列表中的元素数。

另请参阅https://tristanbrindle.com/posts/beware-copies-initializer-list


至于额外的移动构造函数调用,几天前在另一个答案-fno-elide-constructors中讨论了这一点。似乎 g++ 对我在上面引用的同一部分的标准中所示的示例实现采取了一种非常直接的方法。initializer_list

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

于 2014-07-17T00:41:23.907 回答
3

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

正确的移动操作是

Foo(Foo&&) noexcept {cout << "move ctor" << endl;}
Foo& operator=(Foo&&) noexcept {cout << "move assn" << endl;}
于 2014-07-17T00:31:09.883 回答