С 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.
// ...
}
Если тип элемента не копируется с высокой скоростью, или контейнер не соблюдает гарантию строгого исключения, тогда оператор назначения копирования может избежать выделения в случае, когда целевой объект имеет достаточную емкость:
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();
}
На самом деле определены три оператора присваивания:
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 сгенерирует только тогда, когда выбрасывает один из экземпляров элемента.
Как я уже сказал, компилятор может исключить копию для назначения копии. Но компилятор не обязан реализовывать эту оптимизацию. Он обязан реализовать семантику перемещения.