Spec-Zone.ru › C++

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

Оператор перемещающего присваивания — это нешаблонная нестатическая функция-член с именем 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&&, constT&&, volatileT&& или constvolatileT&&
parameter-list-no-default - список параметров, состоящий только из одного параметра типа T&&, constT&&, volatileT&& или constvolatileT&&, не имеющего аргумента по умолчанию
function-body - тело функции оператора перемещающего присваивания
return-type - любой тип, однако 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
};

Оператор перемещающего присваивания вызывается всякий раз, когда он выбирается разрешением перегрузки, например, когда объект находится в левой части выражения присваивания, а в правой части — rvalue того же или неявно преобразуемого типа.

Операторы перемещающего присваивания обычно передают ресурсы, которыми владеет аргумент (например, указатели на динамически выделенные объекты, файловые дескрипторы, TCP-сокеты, дескрипторы потоков и т. д.), вместо создания их копий, оставляя аргумент в некотором допустимом, но в остальном неопределённом состоянии. Поскольку перемещающее присваивание не меняет время жизни аргумента, его деструктор обычно вызывается позднее. Например, перемещающее присваивание из std::string или из std::vector может привести к тому, что аргумент окажется пустым. Перемещающее присваивание определено менее ограничительно, а не более ограничительно, чем обычное присваивание: если после обычного присваивания должны существовать две копии данных, то после перемещающего присваивания должна остаться только одна.

Неявно объявленный оператор перемещающего присваивания

Если для типа класса не предоставлены пользовательские операторы перемещающего присваивания и выполнены все следующие условия:

  • нет объявленных пользователем конструкторов копирования;
  • нет объявленных пользователем конструкторов перемещения;
  • нет объявленных пользователем операторов копирующего присваивания;
  • нет объявленного пользователем деструктора,

то компилятор объявит оператор перемещающего присваивания как член класса 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).

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

Неявно определённый оператор перемещающего присваивания для класса 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 имеет нестатический член данных константного неклассового типа (или, возможно, многомерный массив такого типа).
  • T имеет нестатический член данных ссылочного типа.
  • T имеет потенциально конструируемый подобъект типа класса M (или, возможно, многомерный массив такого типа), для которого разрешение перегрузки при поиске оператора перемещающего присваивания M
    • не находит подходящего кандидата или
    • в случае, если подобъект является вариантным членом, выбирает нетривиальную функцию.

Удалённый неявно объявленный оператор перемещающего присваивания игнорируется при разрешении перегрузки.

Тривиальный оператор перемещающего присваивания

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

  • Он не предоставлен пользователем (то есть неявно определён или задан по умолчанию);
  • T не имеет виртуальных функций-членов;
  • T не имеет виртуальных базовых классов;
  • оператор перемещающего присваивания, выбранный для каждого непосредственного базового класса T, является тривиальным;
  • оператор перемещающего присваивания, выбранный для каждого нестатического члена T классового типа (или массива классового типа), является тривиальным.

Тривиальный оператор перемещающего присваивания выполняет то же действие, что и тривиальный оператор копирующего присваивания, то есть копирует представление объекта, как если бы использовался std::memmove. Все типы данных, совместимые с языком C, допускают тривиальное перемещающее присваивание.

Допустимый оператор перемещающего присваивания

Оператор перемещающего присваивания является допустимым, если он не удалён.

(до C++20)

Оператор перемещающего присваивания является допустимым, если выполнены все следующие условия:

  • Он не удалён.
  • Его связанные ограничения (если есть) выполнены.
  • Не существует оператора перемещающего присваивания с выполненными связанными ограничениями, который был бы более ограничивающим.
(начиная с C++20)

Тривиальность допустимых операторов перемещающего присваивания определяет, является ли класс тривиально копируемым типом.

Примечания

Если предоставлены и оператор копирующего, и оператор перемещающего присваивания, разрешение перегрузки выбирает перемещающее присваивание, если аргумент является rvalue (либо prvalue, например безымянным временным объектом, либо xvalue, например результатом std::move), и выбирает копирующее присваивание, если аргумент является lvalue (именованным объектом или результатом функции/оператора, возвращающего lvalue-ссылку). Если предоставлен только оператор копирующего присваивания, его выбирают для всех категорий аргументов (при условии, что он принимает аргумент по значению или по константной ссылке, поскольку rvalue могут связываться с константными ссылками); в таком случае копирующее присваивание служит запасным вариантом для перемещающего присваивания, когда перемещение недоступно.

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

Дополнительные сведения об ожидаемом поведении пользовательского оператора перемещающего присваивания см. в разделе перегрузка оператора присваивания.

Пример

#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 volatile-подобъект делал оператор перемещающего присваивания,
заданный по умолчанию, нетривиальным (вопрос CWG 496)
тривиальность не затрагивается
CWG 2180 C++11 оператор перемещающего присваивания, заданный по умолчанию для класса T,
не определялся как удалённый, если T является абстрактным и имеет
неприсваиваемые перемещением непосредственные виртуальные базовые классы
в этом случае оператор
определяется как удалённый
CWG 2595 C++20 оператор перемещающего присваивания не считался допустимым, если имеется
другой оператор перемещающего присваивания, который является более
ограничивающим, но его связанные ограничения не выполнены
в этом случае он может быть допустимым
CWG 2690 C++11 неявно определённый оператор перемещающего присваивания для
типов объединений не копировал представление объекта
копирует представление
объекта

См. также

  • конструктор
  • преобразующий конструктор
  • копирующее присваивание
  • конструктор копирования
  • конструктор по умолчанию
  • деструктор
  • инициализация
    • агрегатная инициализация
    • константная инициализация
    • копирующая инициализация
    • инициализация по умолчанию
    • прямая инициализация
    • списочная инициализация
    • инициализация ссылок
    • инициализация значением
    • инициализация нулём
  • конструктор перемещения

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/move_assignment

Spec-Zone.ru

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