Оператор присваивания перемещением
Оператор присваивания перемещением это нешаблонная естатическая функция-элемент с именем operator=, которую можно вызвать с аргументом того же классового типа, и которая копирует содержимое аргумента, возможно, изменяя аргумент.
[править]Синтаксис
Формальный синтаксис оператора присваивания перемещением смотрите в разделе объявление функции. Список синтаксисов ниже демонстрирует только подмножество всех допустимых синтаксисов операторов присваивания перемещением.
возвращаемый-типoperator=( список-параметров ); | (1) | ||||||||
возвращаемый-типoperator=( список-параметров ) тело-функции | (2) | ||||||||
возвращаемый-типoperator=( список-параметров-не-по-умолчанию ) = default; | (3) | ||||||||
возвращаемый-типoperator=( список-параметров ) = delete; | (4) | ||||||||
возвращаемый-типимя-класса :: operator=( список-параметров ) тело-функции | (5) | ||||||||
возвращаемый-типимя-класса :: operator=( список-параметров-не-по-умолчанию ) = default; | (6) | ||||||||
имя-класса | — | класс, оператор перемещения которого объявляется, тип класса указывается как T в описаниях ниже |
список-параметров | — | список параметров только из одного параметра, который имеет тип T&& , const T&&, volatile T&& или constvolatile T&& |
список-параметров-не-по-умолчанию | — | список параметров только из одного параметра, который имеет тип T&& , const T&&, volatile T&& или constvolatile T&& и не имеет аргумента по умолчанию |
тело-функции | — | тело функции оператора присваивания перемещением |
возвращаемый-тип | — | любой тип, но предпочтение отдаётся T& , чтобы можно было выполнять цепочки присваиваний |
[править]Объяснение
struct X { X& operator=(X&& other);// оператор присваивания перемещением// X operator=(const X other); // Ошибка: неверный тип параметра}; union Y {// операторы присваивания перемещением могут иметь синтаксис,// не указанный выше, при условии, что они следуют общему синтаксису// объявления функции и не нарушают ограничения, перечисленные вышеauto operator=(Y&& other)-> Y&;// OK: конечный тип возвращаемого значения Y& operator=(this Y&& self, Y& other);// OK: явный объектный параметр// Y& operator=(Y&&, int num = 1); // Ошибка: есть другие параметры,// не являющиеся объектами};
Оператор присваивания перемещением вызывается всякий раз, когда он выбирается разрешением перегрузки, например, когда объект появляется в левой части выражения присваивания, где правая часть является rvalue того же или неявно преобразуемого типа.
Операторы присваивания перемещением обычно "крадут" ресурсы, содержащиеся в аргументе (например, указатели на динамически размещаемые объекты, дескрипторы файлов, сокеты TCP, потоки ввода-вывода, запущенные потоки и т.д.), а не делают их копии и оставляют аргумент в некотором действительном, но в остальном неопределённом состоянии. Например, присваивание перемещением из std::string или из std::vector может привести к тому, что аргумент останется пустым. Однако это не гарантировано. Присваивание перемещением меньше, а не строго больше, чем обычное присваивание; там, где обычное присваивание должно оставлять две копии данных по завершении, присваивание перемещением требует оставлять только одну.
[править]Неявно объявленный оператор присваивания перемещением
Если для классового типа (struct, class или union) не предоставлены определяемые пользователем операторы присваивания перемещением, и выполняются все следующие условия:
- нет объявленных пользователем конструкторов копирования;
- нет объявленных пользователем конструкторов перемещения;
- нет объявленных пользователем операторов присваивания копированием;
- нет объявленного пользователем деструктора,
тогда компилятор объявит оператор присваивания перемещением как inlinepublic элемент своего класса с сигнатурой T& T::operator=(T&&).
Класс может иметь несколько операторов присваивания перемещением, например, как T& T::operator=(const T&&), так и T& T::operator=(T&&). Если присутствуют некоторые определяемые пользователем операторы присваивания перемещением, пользователь всё равно может принудительно сгенерировать неявно объявленный оператор присваивания перемещением ключевым словом default
.
Неявно объявленный (или заданный по умолчанию при первом объявлении) оператор присваивания перемещения имеет спецификацию исключения, как описано в спецификации динамических исключений(до C++17)спецификации noexcept(начиная с C++17)
Поскольку некоторый оператор присваивания (перемещением или копированием) всегда объявляется для любого класса, оператор присваивания базового класса всегда скрыт. Если для ввода оператора присваивания из базового класса используется using объявление, и его тип аргумента может быть таким же, как тип аргумента неявного оператора присваивания производного класса, using объявление также скрыто неявным объявлением.
[править]Неявно определённый оператор присваивания перемещением
Если неявно объявленный оператор присваивания перемещением не является ни удалённым, ни тривиальным, он определяется (то есть тело функции генерируется и компилируется) компилятором, если используется odr или требуется для константной оценки(начиная с C++14).
Для типов объединения неявно определённый оператор присваивания перемещением копирует представление объекта (как std::memmove).
Для классовых типов не объединений, оператор присваивания перемещением выполняет полное поэлементное присваивание перемещением прямых базовых классов и непосредственных нестатических элементов объекта в порядке их объявления, используя встроенное присваивание для скаляров, присваивание перемещением по элементам для массивов и оператор присваивания перемещением для классовых типов (вызывается невиртуально).
Неявно определённый оператор присваивания перемещением для класса
| (начиная с C++14) (до C++23) |
Неявно определённый оператор присваивания перемещением для класса | (начиная с C++23) |
Как и в случае с копированием, не указано, присваиваются ли виртуальные подобъекты базового класса, доступные более чем по одному пути в решётке наследования, более одного раза с помощью неявно определенного оператора присваивания перемещением:
struct V { V& operator=(V&& other){// это может быть вызвано один или два раза// если вызывается дважды, 'other' это только что перемещённый из V подобъектreturn*this;}}; struct A :virtual V {};// operator= вызывает V::operator=struct B :virtual V {};// operator= вызывает V::operator=struct C : B, A {};// operator= вызывает B::operator=, затем A::operator=// но они могут вызвать V::operator= только один раз int main(){ C c1, c2; c2 = std::move(c1);}
[править]Удалённый оператор присваивания перемещением
Неявно объявленный или заданный по умолчанию оператор присваивания перемещения для класса T
определяется как удалённый, если выполняется любое из следующих условий:
T
имеет нестатический элемент данных, который является const;T
имеет нестатический элемент данных ссылочного типа;T
имеет нестатический элемент данных или прямой базовый класс, которые не могут быть присвоены перемещением (имеют удалённый, недоступный или неоднозначный оператор присваивания перемещением).
Удалённый неявно объявленный оператор присваивания перемещением игнорируется разрешением перегрузки.
[править]Тривиальный оператор присваивания перемещением
Оператор присваивания перемещением для класса T
тривиален, если выполняются все следующие условия:
- Он не предоставляется пользователем (это означает, что он определён неявно или установлен по умолчанию);
T
не имеет виртуальных функций-элементов;T
не имеет виртуальных базовых классов;- оператор присваивания перемещением, выбранный для каждого прямого базового класса класса
T
, тривиален; - оператор присваивания перемещения, выбранный для каждого элемента
T
нестатического классового типа (или массива классового типа), является тривиальным.
Тривиальный оператор присваивания перемещением выполняет то же действие, что и тривиальный оператор присваивания копированием, т.е. делает копию представления объекта, как будто с помощью std::memmove. Все типы данных, совместимые с языком C (типы POD), тривиально присваиваемы перемещением.
[править]Доступный оператор присваивания перемещением
Оператор присваивания перемещением доступен, если он не удалён. | (до C++20) |
Оператор присваивания перемещением доступен, если
| (начиная с C++20) |
Тривиальность доступных операторов присваивания перемещением определяет, является ли класс тривиально копируемым типом.
[править]Примечание
Если предоставлены операторы присваивания копированием и перемещением, разрешение перегрузки выбирает присваивание перемещением, если аргумент является rvalue (либо prvalue например, безымянное временное значение, либо xvalue, такое как результат std::move), и выбирает присваивание копированием, если аргумент является lvalue (именованный объект или функция/оператор, возвращающая ссылку lvalue). Если предоставлено только присваивание копированием, все категории аргументов выбирают его (при условии, что аргумент принимается по значению или как ссылка на const, поскольку значения rvalue могут привязываться к ссылкам const), что делает присваивание копированием запасным вариантом для присваивания перемещением, когда перемещение недоступно.
Не указано, присваиваются ли виртуальные подобъекты базового класса, доступные более чем по одному пути в решётке наследования, более одного раза неявно определённым оператором присваивания перемещением (то же самое относится к присваиванию копированием).
Дополнительные сведения об ожидаемом поведении определяемого пользователем оператора присваивания перемещением смотрите в разделе перегрузка оператора присваивания.
[править]Пример
#include <iostream>#include <string>#include <utility> struct A {std::string s; A(): s("тест"){} A(const A& o): s(o.s){std::cout<<"перемещение не удалось!\n";} A(A&& o): s(std::move(o.s)){} A& operator=(const A& other){ s = other.s;std::cout<<"присваивание копированием\n";return*this;} A& operator=(A&& other){ s = std::move(other.s);std::cout<<"присваивание перемещением\n";return*this;}}; A f(A a){return a;} struct B : A {std::string s2;int n;// неявный оператор присваивания перемещением B& B::operator=(B&&)// вызывает оператор присваивания перемещением для A// вызывает оператор присваивания перемещением для s2// и делает побитовую копию n}; struct C : B { ~C(){}// деструктор предотвращает неявное присваивание перемещением}; struct D : B { D(){} ~D(){}// деструктор предотвращает неявное присваивание перемещением D& operator=(D&&)=default;// всё равно принудительно создаёт// присваивание перемещением}; int main(){ A a1, a2;std::cout<<"Попытка присваивания перемещением A из временного rvalue\n"; a1 = f(A());// присваивание перемещением из временного rvaluestd::cout<<"Попытка присваивания перемещением A из xvalue\n"; a2 = std::move(a1);// присваивание перемещением из xvalue std::cout<<"\nПопытка присваивания перемещением B\n"; B b1, b2;std::cout<<"Перед перемещением, b1.s = \""<< b1.s<<"\"\n"; b2 = std::move(b1);// вызывает неявное присваивание перемещениемstd::cout<<"После перемещения, b1.s = \""<< b1.s<<"\"\n"; std::cout<<"\nПопытка присваивания перемещением C\n"; C c1, c2; c2 = std::move(c1);// вызывает оператор присваивания копированием std::cout<<"\nПопытка присваивания перемещением D\n"; D d1, d2; d2 = std::move(d1);}
Вывод:
Попытка присваивания перемещением A из временного rvalue присваивание перемещением Попытка присваивания перемещением A из xvalue присваивание перемещением Попытка присваивания перемещением B Перед перемещением, b1.s = "тест" присваивание перемещением После перемещения, b1.s = "" Попытка присваивания перемещением C присваивание копированием Попытка присваивания перемещением D присваивание перемещением
[править]Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 1353 | C++11 | условия, в которых операторы присваивания перемещением по умолчанию определяются как удалённые, не учитывали типы многомерных массивов | эти типы рассмотрены |
CWG 1402 | C++11 | оператор присваивания перемещением по умолчанию, который вызывал бы нетривиальный оператор присваивания копированием, был удалён; удалённый оператор присваивания перемещением по умолчанию всё ещё участвует в разрешении перегрузки | разрешает вызов такого оператора присваивания копированием; сделан игнорируемым в разрешении перегрузки |
CWG 1806 | C++11 | отсутствовала спецификация оператора присваивания перемещением по умолчанию, включающего виртуальный базовый класс | добавлено |
CWG 2094 | C++11 | volatile подобъект, делает оператор присваивания перемещением по умолчанию нетривиальным (CWG проблема 496) | тривиальность не затрагивается |
CWG 2180 | C++11 | оператор присваивания перемещением по умолчанию для класса T небыл определён как удалённый, если T являлся абстрактным и имелпрямые виртуальные базовые классы, не присваиваемые перемещением | в этом случае оператор определяется как удалённый |
CWG 2690 | C++11 | неявно определённый оператор присваивания перемещением для типов объединения не копировал представление объекта | он копируют представление объекта |