Почему у 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 будет только бросать, когда бросается один из экземпляров элементов.
Как я уже сказал, компилятор может удалить копию для назначения копии. Но компилятор не обязан выполнять эту оптимизацию. Он обязан реализовать семантику перемещения.