Почему у std::vector есть два оператора присваивания?

С 2011 года у нас есть как копирование, так и перемещение. Однако этот ответ достаточно убедительно доказывает, что для классов управления ресурсами нужен только один оператор присваивания. Например, для std::vector это будет выглядеть как

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

Важным моментом здесь является то, что аргумент берется значением. Это означает, что в настоящий момент вводится собственно тело функции, большая часть работы уже была выполнена конструкцией other (по возможности, по возможности, с помощью конструктора копирования). Следовательно, это автоматически реализует как правильное копирование, так и перемещение.

Если это правильно, почему (согласно эта документация как минимум) std::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.
  // ...
}

Ответы

Ответ 1

Если тип элемента не может быть скопирован или контейнер не соблюдает сильную гарантию исключения, тогда оператор копирования-назначения может избежать выделения в случае, когда целевой объект имеет достаточную емкость:

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();
}

Ответ 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 будет только бросать, когда бросается один из экземпляров элементов.

Как я уже сказал, компилятор может удалить копию для назначения копии. Но компилятор не обязан выполнять эту оптимизацию. Он обязан реализовать семантику перемещения.