Почему 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.
// ...
}

17

Решение

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

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

Другие решения

На самом деле определены три оператора присваивания:

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

Всякий раз, когда копируется какой-либо из элементов throws, эта функция также генерирует throw.

Для реализации оператора перемещения присваивания просто не используйте копирование:

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

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

-2