为什么std :: vector有两个赋值运算符?

时间:2015-11-20 23:01:09

标签: c++ c++11 vector copy-and-swap

自2011年起,我们同时进行复制和移动任务。但是,this answer非常令人信服地认为,对于资源管理类,只需要一个赋值运算符。例如,对于std::vector,这看起来像

vector& vector::operator=(vector other)
{
  swap(other);
  return*this;
}

这里重要的一点是,论证是以价值为基础的。这意味着在输入函数体正确的时刻,大部分工作已经由other的构造完成(如果可能的话,通过移动构造函数,否则通过复制构造函数)。因此,这会自动正确地实现复制和移动分配。

如果这是正确的,为什么(根据this documentation at leaststd::vector 不是以这种方式实施?

编辑以解释其工作原理。请考虑以下示例中上述代码中的other会发生什么

void foo(std::vector<bar> &&x)
{
  auto y=x;             // other is copy constructed
  auto z=std::move(x);  // other is move constructed, no copy is ever made.
  // ...
}

2 个答案:

答案 0 :(得分:7)

如果元素类型不是可复制的,或者容器不遵守强异常保证,那么复制赋值运算符可以避免在目标对象具有足够容量的情况下进行分配:

vector& operator=(vector const& src)
{
    clear();
    reserve(src.size());  // no allocation if capacity() >= src.size()
    uninitialized_copy_n(src.data(), src.size(), dst.data());
    m_size = src.size();
}

答案 1 :(得分:-2)

实际上定义了三个赋值运算符:

vector& operator=( const vector& other );
vector& operator=( vector&& other );
vector& operator=( std::initializer_list<T> ilist );

您的建议vector& vector::operator=(vector other)使用了复制和交换习惯用法。这意味着,当调用操作符时,原始矢量将被复制到参数中,复制矢量中的每个项目。然后,此副本将与this交换。编译器可能能够删除该副本,但该副本省略是可选的,移动语义是标准的。

您可以使用该习惯用法替换复制赋值运算符:

vector& operator=( const vector& other ) {
    swap(vector{other}); // create temporary copy and swap
    return *this;
}

每当复制任何元素抛出时,此函数也会抛出。

要实现移动赋值运算符,请忽略复制:

vector& operator=( vector&& other ) {
    swap(other);
    return *this;
}

由于swap()永远不会抛出,因此移动赋值运算符也不会。

使用移动赋值运算符和匿名临时值也可以轻松实现initializer_list - 赋值:

vector& operator=( std::initializer_list<T> ilist ) {
    return *this = vector{ilist};
}

我们使用了移动赋值运算符。作为一个概念,initializer_list赋值operatpr只会在其中一个元素实例化时抛出。

正如我所说,编译器可能能够删除副本以进行复制分配。但编译器没有义务实现该优化。它有义务实现移动语义。