Оператор перемещения присваивания
Оператор перемещения присваивания — это нешаблонный нестатический член-функция с именем operator=, который может быть вызван с аргументом того же типа класса и копирует содержимое аргумента, возможно, изменяя аргумент.
Синтаксис
Для формального синтаксиса оператора перемещения присваивания см. объявление функции. В списке синтаксиса ниже показан только подмножество всех допустимых синтаксисов оператора перемещения присваивания.
тип_возврата operator=(список_параметров ); | (1) | |
тип_возврата operator=(список_параметров ) тело_функции | (2) | |
тип_возврата operator=(список_параметров_без_значений_по_умолчанию ) = default; | (3) | |
тип_возврата operator=(список_параметров ) = delete; | (4) | |
тип_возврата имя_класса ::operator=(список_параметров ) тело_функции | (5) | |
тип_возврата имя_класса ::operator=(список_параметров_без_значений_по_умолчанию ) = default; | (6) |
| имя_класса | - | класс, для которого объявляется оператор перемещения присваивания, тип класса указан как T в описаниях ниже |
| список_параметров | - | список параметров только с одним параметром, который имеет тип T&&, const T&&, volatile T&& или const volatile T&& |
| список_параметров_без_значений_по_умолчанию | - | список параметров только с одним параметром, который имеет тип T&&, const T&&, volatile T&& или const volatile T&&, и не имеет аргумента по умолчанию |
| тело_функции | - | тело функции оператора перемещения присваивания |
| тип_возврата | - | любой тип, но T& предпочтительнее для возможности цепочки присваиваний |
Объяснение
struct X
{
X& operator=(X&& other); // move assignment operator
// X operator=(const X other); // Error: incorrect parameter type
};
union Y
{
// move assignment operators can have syntaxes not listed above,
// as long as they follow the general function declaration syntax
// and do not viloate the restrictions listed above
auto operator=(Y&& other) -> Y&; // OK: trailing return type
Y& operator=(this Y&& self, Y& other); // OK: explicit object parameter
// Y& operator=(Y&&, int num = 1); // Error: has other non-object parameters
};Оператор перемещения присваивания вызывается всякий раз, когда он выбирается разрешением перегрузки, например, когда объект появляется в левой части выражения присваивания, где правая часть является r-ссылкой того же или неявно конвертируемого типа.
Операторы перемещения присваивания, как правило, «крадут» ресурсы, удерживаемые аргументом (например, указатели на динамически выделенные объекты, дескрипторы файлов, сокеты TCP, потоки ввода-вывода, работающие потоки и т. д.), а не делают их копий, и оставляют аргумент в некотором допустимом, но в противном случае неопределённом состоянии. Например, присвоение перемещением из std::string или из std::vector может привести к тому, что аргумент останется пустым. Однако это не гарантия. Оператор перемещения присваивания определяется менее жёстко, чем обычный оператор присваивания; в то время как обычный оператор присваивания должен оставить две копии данных после завершения, оператор перемещения присваивания должен оставить только одну.
Неявно объявленный оператор перемещения присваивания
Если для типа класса не предоставляются операторы перемещения присваивания, определённые пользователем, и все перечисленные ниже условия выполняются:
- нет объявленных пользователем копирующих конструкторов;
- нет объявленных пользователем конструкторов перемещения;
- нет объявленных пользователем операторов копирования присваивания;
- нет объявленного пользователем деструктора,
то компилятор объявит оператор перемещения присваивания как inline public член своего класса со сигнатурой T& T::operator=(T&&).
Класс может иметь несколько операторов перемещения присваивания, например, как T& T::operator=(const T&&) и T& T::operator=(T&&). Если присутствуют некоторые операторы перемещения присваивания, определённые пользователем, пользователь всё ещё может принудительно сгенерировать неявно объявленный оператор перемещения присваивания с ключевым словом default.
Неявно объявленный (или заданный по умолчанию при первом объявлении) оператор перемещения присваивания имеет спецификацию исключения, как описано в динамической спецификации исключения(до C++17)спецификации noexcept(с C++17).
Поскольку для любого класса всегда объявляется какой-либо оператор присваивания (перемещения или копирования), оператор присваивания базового класса всегда скрыт. Если для использования оператора присваивания из базового класса используется объявление using, и тип его аргумента может совпадать с типом аргумента неявного оператора присваивания производного класса, объявление using также скрывается неявным объявлением.
Неявно определённый оператор перемещения присваивания
Если неявно объявленный оператор перемещения присваивания не удалён и не тривиален, он определяется (то есть, генерируется и компилируется тело функции) компилятором, если используется в соответствии с правилом одного определения или необходим для вычисления константного выражения(с C++14).
Для типов объединения неявно определённый оператор перемещения присваивания копирует представление объекта (как при std::memmove).
Для типов классов, не являющихся объединением, оператор перемещения присваивания выполняет полное членное присвоение перемещением прямых баз и непосредственных нестатических членов объекта в порядке их объявления, используя встроенное присваивание для скаляров, членное присваивание перемещением для массивов и оператор перемещения присваивания для типов классов (вызывается не виртуально).
| Неявно определённый оператор перемещения присваивания для класса
|
(с C++14) (до C++23) |
| Неявно определённый оператор перемещения присваивания для класса | (с C++23) |
Как и при копировании, не определено, будет ли подобъект виртуального базового класса, доступный по нескольким путям в иерархии наследования, присваиваться более одного раза неявно определённым оператором перемещения присваивания:
struct V
{
V& operator=(V&& other)
{
// this may be called once or twice
// if called twice, 'other' is the just-moved-from V subobject
return *this;
}
};
struct A : virtual V {}; // operator= calls V::operator=
struct B : virtual V {}; // operator= calls V::operator=
struct C : B, A {}; // operator= calls B::operator=, then A::operator=
// but they may only call V::operator= once
int main()
{
C c1, c2;
c2 = std::move(c1);
}Удалённый оператор перемещения присваивания
Неявно объявленный или заданный по умолчанию оператор перемещения присваивания для класса T определён как удалённый, если выполняется любое из следующих условий:
-
Tимеет нестатический член данных с квалификатором const нетипа класса (или, возможно, многомерного массива). -
Tимеет нестатический член данных типа ссылки. -
Tимеет возможно построенный подобъект типа классаM(или, возможно, многомерного массива), такой что разрешение перегрузки при поиске оператора перемещения присваиванияM - не приводит к применимому кандидату или
- в случае, если подобъект является членом варианта, выбирает нетривиальную функцию.
Удалённый неявно объявленный оператор перемещения присваивания игнорируется разрешением перегрузки.
Тривиальный оператор перемещения присваивания
Оператор перемещения присваивания для класса T тривиален, если выполняются следующие условия:
- Он не задан пользователем (то есть, он неявно определён или по умолчанию);
-
Tне имеет виртуальных функций-членов; -
Tне имеет виртуальных базовых классов; - оператор перемещения присваивания, выбранный для каждого прямого базового класса
T, является тривиальным; - оператор перемещения присваивания, выбранный для каждого нестатического типа класса (или массива типа класса) члена
T, является тривиальным.
Тривиальный оператор перемещения присваивания выполняет такое же действие, как и тривиальный оператор копирования присваивания, то есть создаёт копию представления объекта, как если бы это было сделано std::memmove. Все типы данных, совместимые с языком C (POD-типы), тривиально присваиваются перемещением.
Оператор перемещения присваивания, допускаемый к использованию
| Оператор перемещения присваивания допускается к использованию, если он не удалён. | (до C++20) |
| Оператор перемещения присваивания допускается к использованию, если
| (с C++20) |
Тривиальность допускаемых к использованию операторов перемещения присваивания определяет, является ли класс типом, тривиально копируемым.
Примечания
Если оба оператора копирования и перемещения присваивания заданы, разрешение перегрузки выбирает оператор перемещения присваивания, если аргумент является справа-значимым (либо неименованный временный, либо xvalue, например, результат std::move ), и выбирает оператор копирования присваивания, если аргумент является слева-значимым (именованный объект или функция/оператор, возвращающая ссылку на объект слева). Если задан только оператор копирования присваивания, все категории аргументов выбирают его (поскольку он принимает аргумент по значению или как ссылку на константу, так как справа-значимые могут привязываться к константным ссылкам), что делает оператор копирования присваивания резервным для оператора перемещения присваивания, когда оператор перемещения недоступен.
Не определено, будет ли подобъект виртуального базового класса, доступный по нескольким путям в иерархии наследования, присваиваться более одного раза оператором перемещения присваивания (то же самое относится к оператору копирования присваивания).
Дополнительные сведения о предполагаемом поведении пользовательского оператора перемещения присваивания см. в статье об перегрузке оператора присваивания.
Пример
#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 << "Trying to move-assign A from rvalue temporary\n";
a1 = f(A()); // move-assignment from rvalue temporary
std::cout << "Trying to move-assign A from xvalue\n";
a2 = std::move(a1); // move-assignment from xvalue
std::cout << "\nTrying to move-assign B\n";
B b1, b2;
std::cout << "Before move, b1.s = \"" << b1.s << "\"\n";
b2 = std::move(b1); // calls implicit move assignment
std::cout << "After move, b1.s = \"" << b1.s << "\"\n";
std::cout << "\nTrying to move-assign C\n";
C c1, c2;
c2 = std::move(c1); // calls the copy assignment operator
std::cout << "\nTrying to move-assign D\n";
D d1, d2;
d2 = std::move(d1);
}Вывод:
Trying to move-assign A from rvalue temporary move assigned Trying to move-assign A from xvalue move assigned Trying to move-assign B Before move, b1.s = "test" move assigned After move, b1.s = "" Trying to move-assign C copy assigned Trying to move-assign D move assigned
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применено к | Поведение, опубликованное | Правильное поведение |
|---|---|---|---|
| CWG 1353 | C++11 | условия, при которых операторы перемещения присваивания по умолчанию определялись как удалённые, не учитывали многомерные типы массивов | учитывать эти типы |
| CWG 1402 | C++11 | оператор перемещения присваивания по умолчанию, который бы вызвал нетривиальный оператор копирования присваивания, был удалён; оператор перемещения присваивания по умолчанию, который удалён, всё ещё участвовал в разрешении перегрузки | разрешает вызов такого оператора копирования присваивания; сделал игнорируемым в разрешении перегрузки |
| CWG 1806 | C++11 | недоставало спецификации для оператора перемещения присваивания по умолчанию, включающего виртуальный базовый класс | добавлено |
| CWG 2094 | C++11 | подробный подобъект сделал оператор перемещения присваивания по умолчанию нетривиальным (CWG issue 496) | тривиальность не затронута |
| CWG 2180 | C++11 | оператор перемещения присваивания по умолчанию для класса Tне определялся как удалённый, если T является абстрактным и имеетнеприсваиваемые перемещением прямые виртуальные базовые классы | оператор определён как удалённый в этом случае |
| CWG 2690 | C++11 | неявный оператор перемещения присваивания для типов объединения не копировал представление объекта | они копируют представление объекта |
См. также
- конструктор
- конвертирующий конструктор
- оператор копирования присваивания
- конструктор копирования
- конструктор по умолчанию
- деструктор
- инициализация
- конструктор перемещения
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/move_assignment