为什么从初始化列表中初始化向量时不使用移动构造(通过隐式构造函数)

Why isn't move construction used when initiating a vector from initializer list (via implicit constructor)

为了演示移动语义,我编写了以下示例代码,其中包含来自 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 的初始化列表,不能 从中移动(因为它的 const ).这是正确的解释吗? - 怎么回事?

Live demo

想多了一点。这是自我回答:

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

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

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

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

I thought I had an initializer list of integers, but I'm now wondering if what I have in between is an initializer list of Cs, which can't be moved from (as its const). Is this a correct interpretation?

这是正确的。 vector<C> 没有 initializer_list<int> 构造函数,甚至没有 initializer_list<T> 某些模板参数 T 的构造函数。它确实有一个 initializer_list<C> 构造函数 - 它由您传入的所有整数构建而成。由于 initializer_list 的支持是一个 const 数组,您得到一堆副本而不是一堆移动。

如我的评论所述,您获得了副本,因为类型 std::vector<C> 的向量需要一个 std::initializer_list<C>,因此您的 int 列表被构建为 int 的临时列表=14=]'s 并且它是 C's 的列表,正在从中复制。

解决此问题的一种方法是创建辅助函数。使用像

这样的东西
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,那么您甚至无法将向量移出函数。请参阅此 live example 了解 g++ 的完整输出。请注意,我打印了构造函数,这样您就可以看到它是唯一被调用的。