【问题标题】:std::vector initialization move/copy constructor of the elementstd::vector 初始化元素的移动/复制构造函数
【发布时间】:2014-09-07 16:53:25
【问题描述】:

我有这段代码:

#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 两次。有任何想法吗?

【问题讨论】:

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


    【解决方案1】:
    vector<Foo> v{std::move(foo)};
    

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

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

    std::initializer_list&lt;E&gt; 类型的对象是从初始化列表构造的,就好像实现分配了一个由 N 类型为 const E 的元素组成的临时数组,其中 N 是初始化列表中的元素。

    另见https://tristanbrindle.com/posts/beware-copies-initializer-list


    关于-fno-elide-constructors 的额外移动构造函数调用,几天前在another answer 中讨论过。似乎 g++ 对initializer_list 的示例实现采取了一种非常直接的方法,该示例实现在我上面引用的同一部分的标准中显示。

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

    【讨论】:

    • 哦,明白了...统一初始化的语法{...} 相当棘手,我现在意识到vector 不是聚合。是的,我的clang也是如此,没有额外的动作。很好的答案,谢谢!
    • 我同意 T.C.:gcc 产生两个移动构造函数调用的事实似乎是一个错误。但我想只要这种行为只有在您通过-fno-elide-constructors 时才能观察到,就没有问题。
    • T.C. 的回答很到位,我同意 clang 的行为似乎是正确的。以下代码应相同:Foo arr[1] = { std::move(foo) }; vector&lt;Foo&gt; v(arr, arr + 1);initializer_list 应该只用于保存arrarr + 1,而不是移动/复制元素。
    • @MattMcNabb 我同意,即使 gcc'c 的行为没有错误,也绝对是奇怪的,clang 的似乎更直观。无论如何,正如 Brian 所说,只要它只发生在 -fno-elide-constructors 上,就不太可能打扰到任何人。
    【解决方案2】:

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

    正确的移动操作是

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

    【讨论】:

    • 谢谢,我改了,现在所有的ctors(连同添加的operator=)都标记为noexcept,但是我仍然得到完全相同的输出(案例1)和2)更多)。我用g++4.9顺便说一句。
    • @vsoftco:Praetorian 想通了,点击接受他的回答。
    猜你喜欢
    • 2020-05-05
    • 2018-03-12
    • 2020-09-03
    • 2012-06-09
    • 1970-01-01
    • 1970-01-01
    • 2015-08-05
    • 1970-01-01
    相关资源
    最近更新 更多