Move assignment operator
Оператор перемещающего присваивания — это нешаблонная нестатическая функция-член с именем operator = , которая может быть вызвана с аргументом того же типа класса и копирует содержимое аргумента, возможно, изменяя его.
Синтаксис
Для синтаксиса формального оператора перемещающего присваивания см. объявление функции . Приведённый ниже список синтаксисов демонстрирует только подмножество всех допустимых вариантов синтаксиса оператора перемещающего присваивания.
return-type
operator=(
parameter-list
);
|
(1) | ||||||||
return-type
operator=(
parameter-list
)
function-body
|
(2) | ||||||||
return-type
operator=(
parameter-list-no-default
) = default;
|
(3) | ||||||||
return-type
operator=(
parameter-list
) = delete;
|
(4) | ||||||||
return-type
class-name
::
operator=(
parameter-list
)
function-body
|
(5) | ||||||||
return-type
class-name
::
operator=(
parameter-list-no-default
) = default;
|
(6) | ||||||||
| class-name | - |
класс, для которого объявляется оператор перемещающего присваивания, тип класса указан как
T
в описаниях ниже
|
| parameter-list | - |
список параметров
только с одним параметром, который имеет тип
T&&
,
const
T
&&
,
volatile
T
&&
или
const
volatile
T
&&
|
| parameter-list-no-default | - |
список параметров
только с одним параметром, который имеет тип
T&&
,
const
T
&&
,
volatile
T
&&
или
const
volatile
T
&&
и не имеет аргумента по умолчанию
|
| function-body | - | тело функции оператора перемещающего присваивания |
| return-type | - |
любой тип, но предпочтительно
T&
для согласованности с типами Scala
|
Объяснение
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 может привести к тому, что аргумент останется пустым. Перемещающее присваивание определено менее, а не более ограничительно, чем обычное присваивание; там, где обычное присваивание должно оставить две копии данных после завершения, перемещающее присваивание требует оставить только одну.
Неявно объявленный оператор перемещающего присваивания
Если для типа класса не предоставлены определенные пользователем операторы перемещающего присваивания и все следующее верно:
- нет пользовательских copy constructors ;
- нет пользовательских move constructors ;
- нет пользовательских copy assignment operators ;
- нет пользовательского destructor ,
тогда компилятор объявит оператор перемещающего присваивания как inline public член своего класса с сигнатурой T & T :: operator = ( T && ) .
Класс может иметь несколько операторов перемещающего присваивания, например, как
T
&
T
::
operator
=
(
const
T
&&
)
, так и
T
&
T
::
operator
=
(
T
&&
)
. Если присутствуют пользовательские операторы перемещающего присваивания, пользователь всё равно может принудительно сгенерировать неявно объявленный оператор присваивания с помощью ключевого слова
default
.
Неявно объявленный оператор перемещающего присваивания имеет спецификацию исключений, как описано в динамической спецификации исключений (до C++17) спецификации noexcept (начиная с C++17) .
Поскольку для любого класса всегда объявлен некоторый оператор присваивания (перемещения или копирования), оператор присваивания базового класса всегда скрыт. Если using-объявление используется для импорта оператора присваивания из базового класса, и его тип аргумента может совпадать с типом аргумента неявного оператора присваивания производного класса, using-объявление также скрывается неявной декларацией.
Неявно определённый оператор перемещающего присваивания
Если неявно объявленный оператор перемещающего присваивания не является ни удаленным, ни тривиальным, он определяется (то есть, тело функции генерируется и компилируется) компилятором, если odr-used или needed for constant evaluation (since 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имеет потенциально конструируемый подобъект классового типаM(или, возможно, многомерный массив такого типа), для которого разрешение перегрузки, применённое для поиска оператора перемещающего присваиванияM
-
- не приводит к появлению пригодного кандидата, или
- в случае, когда подобъект является variant member , выбирает нетривиальную функцию.
Удаленный неявно объявленный оператор перемещающего присваивания игнорируется при разрешении перегрузки .
Тривиальный оператор перемещающего присваивания
Оператор перемещающего присваивания для класса
T
является тривиальным, если выполняются все следующие условия:
- Он не предоставлен пользователем (то есть является неявно определённым или заданным по умолчанию);
-
Tне имеет виртуальных функций-членов; -
Tне имеет виртуальных базовых классов; -
оператор перемещающего присваивания, выбранный для каждого прямого базового класса
T, является тривиальным; -
оператор перемещающего присваивания, выбранный для каждого нестатического члена классового типа (или массива классового типа)
T, является тривиальным.
Тривиальный оператор перемещающего присваивания выполняет то же действие, что и тривиальный оператор копирующего присваивания, то есть создаёт копию представления объекта, как если бы с помощью std::memmove . Все типы данных, совместимые с языком C, являются тривиально перемещаемо присваиваемыми.
Подходящий оператор перемещающего присваивания
|
Оператор перемещающего присваивания является подходящим, если он не удалён. |
(до C++20) |
|
Оператор перемещающего присваивания является подходящим, если удовлетворены все следующие условия:
|
(начиная с C++20) |
Тривиальность подходящих операторов перемещающего присваивания определяет, является ли класс тривиально копируемым типом .
Примечания
Если предоставлены и оператор копирующего присваивания, и оператор перемещающего присваивания, разрешение перегрузки выбирает перемещающее присваивание, если аргумент является rvalue (либо prvalue , такой как безымянный временный объект, либо xvalue , такой как результат std::move ), и выбирает копирующее присваивание, если аргумент является lvalue (именованный объект или функция/оператор, возвращающие lvalue-ссылку). Если предоставлен только оператор копирующего присваивания, все категории аргументов выбирают его (при условии, что он принимает аргумент по значению или по ссылке на константу, поскольку rvalue могут связываться с константными ссылками), что делает копирующее присваивание резервным вариантом для перемещающего присваивания, когда перемещение недоступно.
Не определено, присваиваются ли подобъекты виртуальных базовых классов, доступные через более чем один путь в решетке наследования, более одного раза неявно определенным оператором перемещающего присваивания (то же относится к копирующему присваиванию ).
См. перегрузку оператора присваивания для получения дополнительной информации о предполагаемом поведении пользовательского move-assignment operator.
Пример
#include <iostream> #include <string> #include <utility> struct A { std::string s; A() : s("test") {} A(const A& o) : s(o.s) { std::cout << "move failed!\n"; } A(A&& o) : s(std::move(o.s)) {} A& operator=(const A& other) { s = other.s; std::cout << "copy assigned\n"; return *this; } A& operator=(A&& other) { s = std::move(other.s); std::cout << "move assigned\n"; return *this; } }; A f(A a) { return a; } struct B : A { std::string s2; int n; // implicit move assignment operator B& B::operator=(B&&) // calls A's move assignment operator // calls s2's move assignment operator // and makes a bitwise copy of n }; struct C : B { ~C() {} // destructor prevents implicit move assignment }; struct D : B { D() {} ~D() {} // destructor would prevent implicit move assignment D& operator=(D&&) = default; // force a move assignment anyway }; int main() { A a1, a2; std::cout << "Попытка перемещающего присваивания A из rvalue временного объекта\n"; a1 = f(A()); // move-assignment from rvalue temporary std::cout << "Попытка перемещающего присваивания A из xvalue\n"; a2 = std::move(a1); // move-assignment from xvalue std::cout << "\nПопытка перемещающего присваивания B\n"; B b1, b2; std::cout << "До перемещения, b1.s = \"" << b1.s << "\"\n"; b2 = std::move(b1); // calls implicit move assignment std::cout << "После перемещения, b1.s = \"" << b1.s << "\"\n"; std::cout << "\nПопытка перемещающего присваивания C\n"; C c1, c2; c2 = std::move(c1); // calls the copy assignment operator std::cout << "\nПопытка перемещающего присваивания D\n"; D d1, d2; d2 = std::move(d1); }
Вывод:
Попытка перемещающего присваивания A из rvalue временного объекта move assigned Попытка перемещающего присваивания A из xvalue move assigned Попытка перемещающего присваивания B До перемещения, b1.s = "test" move assigned После перемещения, b1.s = "" Попытка перемещающего присваивания C copy assigned Попытка перемещающего присваивания D move assigned
Отчеты о дефектах
Следующие отчеты об изменениях в поведении, являющиеся дефектными, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Applied to | Behavior as published | Correct behavior |
|---|---|---|---|
| CWG 1353 | C++11 |
условия, при которых операторы перемещающего присваивания по умолчанию
определяются как удалённые, не учитывали многомерные массивы |
учитывать эти типы |
| CWG 1402 | C++11 |
оператор перемещающего присваивания по умолчанию, который бы
вызывал нетривиальный оператор копирующего присваивания, удалялся; удалённый оператор перемещающего присваивания по умолчанию всё равно участвовал в разрешении перегрузки |
разрешает вызов такого
оператора копирующего присваивания; игнорируется при разрешении перегрузки |
| CWG 1806 | C++11 |
спецификация для оператора перемещающего присваивания по умолчанию
с участием виртуального базового класса отсутствовала |
добавлена |
| CWG 2094 | C++11 |
volatile подобъект делал оператор перемещающего присваивания
по умолчанию нетривиальным ( CWG issue 496 ) |
тривиальность не затрагивается |
| CWG 2180 | C++11 |
оператор перемещающего присваивания по умолчанию для класса
T
не определялся как удалённый, если
T
абстрактный и имеет
неперемещаемые прямые виртуальные базовые классы |
оператор определяется
как удалённый в этом случае |
| CWG 2595 | C++20 |
оператор перемещающего присваивания не был доступен, если существует
другой оператор перемещающего присваивания, который более ограничен, но не удовлетворяет своим ассоциированным ограничениям |
может быть доступен в этом случае |
| CWG 2690 | C++11 |
неявно определённый оператор перемещающего присваивания для
объединений не копировал представление объекта |
копирует представление
объекта |