Spec-Zone.ru › C++

Оператор перемещения присваивания

Оператор перемещения присваивания — это нешаблонный нестатический член-функция с именем 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& предпочтительнее для возможности цепочки присваиваний

Объяснение

1) Объявление оператора перемещения присваивания внутри определения класса.
2-4) Определение оператора перемещения присваивания внутри определения класса.
3) Оператор перемещения присваивания явным образом задан по умолчанию.
4) Оператор перемещения присваивания удален.
5,6) Определение оператора перемещения присваивания вне определения класса (класс должен содержать объявление (1)).
6) Оператор перемещения присваивания явным образом задан по умолчанию.
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).

Для типов классов, не являющихся объединением, оператор перемещения присваивания выполняет полное членное присвоение перемещением прямых баз и непосредственных нестатических членов объекта в порядке их объявления, используя встроенное присваивание для скаляров, членное присваивание перемещением для массивов и оператор перемещения присваивания для типов классов (вызывается не виртуально).

Неявно определённый оператор перемещения присваивания для класса T является constexpr, если

  • T является литеральным типом, и
  • оператор присваивания, выбранный для перемещения каждого подобъекта базового класса, является функцией constexpr, и
  • для каждого нестатического данных члена T, который является классом (или массивом), оператор присваивания, выбранный для перемещения этого члена, является функцией constexpr.
(с C++14)
(до C++23)

Неявно определённый оператор перемещения присваивания для класса T является constexpr.

(с 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)

Оператор перемещения присваивания допускается к использованию, если

  • он не удалён, и
  • его связанные ограничения, если таковые имеются, удовлетворяются, и
  • ни один оператор перемещения присваивания с тем же типом первого параметра и теми же квалификаторами cv/ссылки (если таковые имеются) не является более ограниченным, чем он.
(с 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API