std::带大括号的向量初始化调用复制构造函数两次

问题描述 投票:0回答:3

为什么当我用大括号初始化 std::vector 时

std::vector<TS> vec {ts1, ts2};

编译器调用两次复制构造函数运算符?另一方面 - 使用push_back它只调用一次。

#include <iostream>
#include <vector>
using namespace std;

struct TS{
    TS(){
        cout<<"default constructor\n";
    }

    TS(const TS &other) {
        cout<<"Copy constructor\n";
    }

    TS(TS &&other) noexcept{
        cout<<"Move constructor\n";
    }

    TS& operator=(TS const& other)
    {
        cout<<"Copy assigment\n";
        return *this;
    }

    TS& operator=(TS const&& other) noexcept
    {
        cout<<"Move assigment\n";
        return *this;
    }

    ~TS(){
        cout<<"destructor\n";
    }

};

int main() {
    TS ts1;
    TS ts2;
    cout<<"-----------------------------------------\n";
    std::vector<TS> vec {ts1, ts2};
    //vec.push_back(ts1);
    //vec = {ts1, ts2};
    cout<<"-----------------------------------------\n";



    return 0;
}

http://ideone.com/qcPG7X

c++ stdvector curly-braces list-initialization
3个回答
5
投票

据我了解,

initializer_list
通过常量引用传递所有内容。从其中
move
可能不安全。
initializer_list
vector
构造函数将复制每个元素。

以下是一些链接: initializer_list 和移动语义

不,这不会按预期工作;您仍然会得到副本。我很漂亮 对此感到惊讶,因为我认为初始化器列表的存在是为了 保留一系列临时对象,直到它们被移动为止。

initializer_list 的 begin 和 end 返回 const T *,所以结果 代码中的移动是 T const && — 一个不可变的右值引用。这样的 无法有意义地移动表达式。它将绑定到一个 T const & 类型的函数参数,因为右值确实绑定到 const 左值引用,您仍然会看到复制语义。

移动初始值设定项列表的元素安全吗?

initializer_list 仅提供对其元素的 const 访问。你可以 使用 const_cast 使该代码编译,但随后移动可能会结束 未定义的行为(如果initializer_list的元素 是真正的常量)。所以,不,这样做是不安全的。有 如果您确实需要的话,可以找到解决方法。

我可以列表初始化仅移动类型的向量吗?

18.9 的概要已经说得相当清楚了 初始化列表的元素总是通过 const 引用。不幸的是,似乎没有任何办法 在当前的初始值设定项列表元素中使用 move-semantic 语言修订。

有关 std::initializer_list 设计的问题

来自 C++ 标准第 18.9 节:

initializer_list 类型的对象提供对 const E 类型的对象数组的访问。 [ 注意:一对指针或一个指针加 长度是初始化器列表的明显表示。 initializer_list 用于实现指定的初始值设定项列表 在 8.5.4 中。复制初始值设定项列表不会复制底层 元素。 — 尾注]

我认为大多数这些事情的原因是 std::initializer_list 实际上不是一个容器。它没有 值语义,它有指针语义。显而易见的是 引用的最后一部分:复制初始值设定项列表不会 复制底层元素。因为它们的目的只是为了 初始化事物的目的,我认为这并不令人惊讶 您无法获得更强大的容器的所有优点,例如 元组。

如果我正确理解了最后一部分,这意味着需要两组副本,因为

initializer_list
不会复制底层元素。(只有当您尝试使用
initializer_list
而不复制出时,前面的引用才相关)元素。)

std::initializer_list的底层结构是什么?

不,你不能从initializer_list的元素中移动,因为 初始化列表的元素应该是不可变的(参见 上面引用的段落的第一句话)。这也是原因 为什么只有 const 限定的成员函数才能让您访问 元素。


如果你愿意,你可以使用

emplace_back

vec.emplace_back(TS());
vec.emplace_back(TS());
vec.push_back(std::move(ts1));
vec.push_back(std::move(ts2));

1
投票

因为那里有两个元素。


0
投票

在您提供的代码中,使用Microsoft编译器,当TS位于向量初始值设定项列表中时,TS的复制构造函数将仅被调用一次!

TS ts1; // 构造函数调用 std::向量 vec {ts1}; // 调用复制构造函数。只有一个。

如果您在初始化列表中创建一个对象,它将同时执行以下操作:

std::向量 vec {TS()}; // 构造函数,然后调用复制构造函数

在第二种情况下,如果有多个对象,将首先调用所有构造函数,然后调用所有复制构造函数。

© www.soinside.com 2019 - 2024. All rights reserved.