std::vector initialization元素的move/copy构造函数



我有一段代码:

#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?在第二种情况下,当编译器不执行省略时,我完全不知道为什么移动ctor被调用两次。有什么想法吗?

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

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

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

std::initializer_list<E>类型的对象是根据初始化器列表构造的,就好像实现分配了const E类型的N元素的临时数组一样,其中N是初始化器列表中的元素数。

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


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

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

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

正确的移动操作是

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

最新更新