【问题标题】:Why isn't move construction used when initiating a vector from initializer list (via implicit constructor)为什么从初始化列表初始化向量时不使用移动构造(通过隐式构造函数)
【发布时间】:2017-06-13 05:05:41
【问题描述】:

为了演示移动语义,我编写了以下示例代码,其中包含来自 int 的隐式构造函数。

struct C {
  int i_=0;
  C() {}
  C(int i) : i_( i ) {}
  C( const C& other) :i_(other.i_) {
    std::cout << "A copy construction was made." << i_<<std::endl;
  }
  C& operator=( const C& other) {
    i_= other.i_ ;
    std::cout << "A copy assign was made."<< i_<<std::endl;
    return *this;
  }
  C( C&& other ) noexcept :i_( std::move(other.i_)) {
    std::cout << "A move construction was made." << i_ << std::endl;
  }
  C& operator=( C&& other ) noexcept {
    i_ = std::move(other.i_);
    std::cout << "A move assign was made." << i_ << std::endl;
    return *this;
  }
};

还有

auto vec2 = std::vector<C>{1,2,3,4,5};
cout << "reversing\n";
std::reverse(vec2.begin(),vec2.end());

有输出

A copy construction was made.1
A copy construction was made.2
A copy construction was made.3
A copy construction was made.4
A copy construction was made.5
reversing
A move construction was made.1
A move assign was made.5
A move assign was made.1
A move construction was made.2
A move assign was made.4
A move assign was made.2

现在,反面显示了 2 两次交换(每个使用一个移动分配和两个移动构造),但是为什么从初始化列表创建的临时 C 对象无法移动?我以为我有一个整数的初始化列表,但我现在想知道我之间是否有一个 Cs 的初始化列表,不能从中移动(作为它的常量)。这是一个正确的解释吗? - 怎么回事?

Live demo

【问题讨论】:

  • 很确定 this 是您遇到的问题。
  • @NathanOliver,这是关于从 C 的初始化列表中移动,但我认为我有一个整数列表,用于构建临时 C 对象。
  • std::vector&lt;C&gt; 类型的向量需要 std::initializer_list&lt;C&gt;,因此您的 int 列表被构造为 C 的临时列表,并且正是从中复制的 C 列表。至少这是我认为代码必须要做的事情。
  • 初始化器列表构造对非聚合进行复制。就是这样,对不起。
  • std::vector&lt;C&gt; 没有采用 std::initializer_list&lt;int&gt; 的构造函数(但它有一个用于 std::initializer_list&lt;C&gt; 的构造函数)。

标签: c++ c++14 move-semantics initializer-list


【解决方案1】:

我以为我有一个整数的初始化列表,但我现在想知道我之间是否有一个 Cs 的初始化列表,它不能被移出(作为它的 const)。这是一个正确的解释吗?

这是正确的。 vector&lt;C&gt; 没有 initializer_list&lt;int&gt; 构造函数,甚至没有 initializer_list&lt;T&gt; 某些模板参数 T 的构造函数。它所拥有的是一个initializer_list&lt;C&gt; 构造函数——它是由你传入的所有整数组成的。由于initializer_list 的支持是一个常量数组,你得到的是一堆副本而不是一堆动作。

【讨论】:

    【解决方案2】:

    正如我的评论中详述的那样,您会得到副本,因为std::vector&lt;C&gt; 类型的向量需要std::initializer_list&lt;C&gt;,因此您的int 列表被构造为C 的临时列表,就是这样正在复制的C 的列表。

    您可以解决这个问题的一种方法是创建一个辅助函数。使用类似的东西

    template <typename T, typename Y>
    std::vector<T> emplace_list(std::initializer_list<Y> list)
    {
        std::vector<T> temp;
        temp.reserve(list.size());
        for (const auto& e: list)
            temp.emplace_back(e);
        return temp;
    }
    
    int main()
    {
        auto vec2 = emplace_list<C>({1,2,3,4,5});
    }
    

    您可以避免复制列表中的元素,因为您使用emplace_back 直接将它们构造到向量中。如果编译器应用 NRVO,那么您甚至不会将向量移出函数。有关 g++ 的完整输出,请参阅此 live example。请注意,我打印了构造函数,以便您可以看到它是唯一被调用的。

    【讨论】:

      【解决方案3】:

      想多了。这是自己的答案:

      std::vector&lt;C&gt; 没有initializer_list&lt;int&gt; 的构造函数,也没有可转换为C 的T 事件。它确实有constructor

      vector( std::initializer_list<T> init,
          const Allocator& alloc = Allocator() );
      

      因此,initializer_list 参数列表将是 initializer_list&lt;C&gt;。所说的构造函数除了从初始化列表中复制之外什么也做不了,因为它们是不可变的(以前说的是 const,但这里的效果是一样的,不能从中移动)。

      哦,这也是我写这篇文章时 NathanOliver 在评论中所写的。

      【讨论】:

      • 您发布的代码中唯一的 const 是分配器。复制的真正原因是因为列表初始化只复制(对于非聚合,C 是因为您包含了用户定义的构造函数),句号。
      • @AndyG 这不对。列表初始化不只是复制(X x{std::move(y)} 很好),std::initializer_list 是隐式 const。
      • @Barry:我的意思是从std::initializer_list 构造,有没有更简单的方法来说明这一点?
      • @AndyG 不。这有点令人困惑,因为有 list-initialization、initializer-liststd::initializer_list,它们都是不同的东西,尽管相关。只是要小心。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-01
      • 2020-12-27
      • 1970-01-01
      相关资源
      最近更新 更多