【问题标题】:std::vector init with braces call copy constructor twice带大括号的 std::vector init 调用复制构造函数两次
【发布时间】:2013-12-10 17:48:02
【问题描述】:

为什么当我用大括号初始化 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

【问题讨论】:

  • 不应使用大括号初始化调用赋值运算符,但将参数复制到 std::initializer_list&lt;TS&gt; 并复制出 => 每个参数调用两次复制构造函数。您可以将它们移入initializer_list (std::vector&lt;TS&gt; vec {std::move(ts1), std::move(ts2)};),但不能将它们移出,因此至少需要一个复制构造函数调用。
  • 我至少可以强迫它使用移动分配,而不是应付吗?
  • 不适用于列表初始化。但是,您可以编写一个变通方法,例如返回一个类型的函数,该类型将函数的参数存储为引用,并提供到 std::vector 的转换,称为 vector&lt;TS&gt; vec( collect_references(ts1, ts2) );
  • @DyP 函数应该返回 std::vector。好的,好主意,谢谢。
  • 不,因为现在不是value_type。它应该返回一个带有template&lt;class T&gt; operator std::vector&lt;T&gt;() const 的类型以推断 value_typefor 要初始化的向量;然后它可以使用emplace_back 并尽可能避免any copy-ctor。 Live example

标签: c++ stdvector curly-braces list-initialization


【解决方案1】:

据我了解,initializer_lists 通过 const-reference 传递所有内容。从一开始move 可能不安全。 vectorinitializer_list 构造函数将复制每个元素。

这里有一些链接: initializer_list and move semantics

不,这不会按预期工作;你仍然会得到副本。我很漂亮 对此感到惊讶,因为我认为 initializer_list 存在于 保留一组临时对象,直到它们被移动。

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

Is it safe to move elements of a initializer list?

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

Can I list-initialize a vector of move-only type?

18.9 中的概要使其相当清晰 初始化器列表的元素总是通过 常量引用。不幸的是,似乎没有任何方法 在当前的初始化列表元素中使用移动语义 语言的修订。

questions regarding the design of std::initializer_list

来自 C++ 标准的第 18.9 节:

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

我认为大多数这些事情的原因是 std::initializer_list 实际上不是容器。它没有 值语义,它具有指针语义。这很明显 引用的最后一部分:复制初始化列表不会 复制底层元素。看到它们仅用于 初始化事物的目的,我认为这并不奇怪 你没有得到更强大的容器的所有细节,例如 元组。

如果我对最后一部分的理解正确,这意味着需要两组副本,因为initializer_list 不会复制底层元素。(前面的引用仅在您尝试使用时才相关initializer_list 不复制元素。)

What is the underlying structure of std::initializer_list?

不,您不能从 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));

【讨论】:

  • 我确实可以使用 emplace_back。但是使用 {} - 它看起来更漂亮。
【解决方案2】:

因为那里有两个元素。

【讨论】:

  • 每个参数都会调用copy-ctor 两次,所以这两个参数总共调用了四次。
  • 问得不好,然后
  • OP 甚至说“复制赋值运算符”;)
猜你喜欢
  • 2012-11-03
  • 2016-08-12
  • 1970-01-01
  • 2015-09-25
  • 2013-03-02
  • 2018-03-12
  • 1970-01-01
  • 2015-06-29
  • 2020-10-05
相关资源
最近更新 更多