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

std::vector initialization move/copy constructor of the element

提问人:vsoftco 提问时间:7/17/2014 最后编辑:vsoftco 更新时间:11/9/2020 访问量:3341

问:

我有这段代码:

#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_backv

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 被调用两次。有什么想法吗?

C++ C++11 移动语义复制 省略

评论


答:

3赞 Mooing Duck 7/17/2014 #1

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

正确的移动操作是

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

评论

0赞 vsoftco 7/17/2014
谢谢,我更改了它,现在所有 ctor(连同添加的)都标记为 ,但是我仍然得到完全相同的输出(案例 1)和上面的 2)。顺便说一句,我用。operator=noexceptg++4.9
0赞 Mooing Duck 7/17/2014
@vsoftco:禁卫军想通了,点击接受他的答案。
12赞 Praetorian 7/17/2014 #2
vector<Foo> v{std::move(foo)};

在这里,您调用的向量构造函数采用 .初始值设定项列表只允许访问其元素,因此必须将每个元素从 复制到它自己的存储中。这就是导致调用复制构造函数的原因。std::initializer_listconstvectorinitializer_list

§8.5.4/5 [dcl.init.list]

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

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


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

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

评论

0赞 vsoftco 7/17/2014
哦,知道了......统一初始化的语法非常棘手,我现在意识到这不是一个聚合。是的,我的叮当声也一样,没有多余的动作。很好的答案,谢谢!{...}vector
0赞 Brian Bi 7/17/2014
我同意 T.C. 的观点:gcc 生成两个移动构造函数调用的事实似乎是一个错误。但我想只要这种行为只有在你通过时才能观察到,就没有问题。-fno-elide-constructors
0赞 M.M 7/17/2014
T.C.的回答很明确,我同意clang的行为似乎是正确的。以下代码应相同:。应仅用于保存和,而不是移动/复制元素。Foo arr[1] = { std::move(foo) }; vector<Foo> v(arr, arr + 1);initializer_listarrarr + 1
0赞 Praetorian 7/17/2014
@MattMcNabb我同意,即使 gcc'c 的行为没有不正确,也绝对很奇怪,clang 似乎更直观。无论如何,正如布莱恩所说,只要它只发生在 ,就不太可能打扰任何人。-fno-elide-constructors