Spec-Zone.ru › C++

Конструкторы перемещения

Конструктор перемещения — это конструктор, который может быть вызван с аргументом того же типа класса и копирует содержимое аргумента, возможно, изменяя аргумент.

Синтаксис

имя-класса (список-параметров ); (1)
имя-класса (список-параметров ) тело-функции (2)
имя-класса (список-с-одним-параметром ) = default; (3)
имя-класса (список-параметров ) = delete; (4)
имя-класса ::имя-класса (список-параметров ) тело-функции (5)
имя-класса ::имя-класса (список-с-одним-параметром ) = default; (6)
имя-класса - класс, для которого объявляется конструктор перемещения
список-параметров - непустой список параметров, удовлетворяющий всем следующим условиям:
  • учитывая тип класса как T, первый параметр имеет тип T&&, const T&&, volatile T&& или const volatile T&&, и
  • либо нет других параметров, либо все остальные параметры имеют значения по умолчанию
список-с-одним-параметром - список параметров только с одним параметром, который имеет тип T&&, const T&&, volatile T&& или const volatile T&& и не имеет значения по умолчанию
тело-функции - тело функции конструктора перемещения

Объяснение

1) Объявление конструктора перемещения внутри определения класса.
2-4) Определение конструктора перемещения внутри определения класса.
3) Конструктор перемещения явным образом задан по умолчанию.
4) Конструктор перемещения удален.
5,6) Определение конструктора перемещения вне определения класса (класс должен содержать объявление (1)).
6) Конструктор перемещения явным образом задан по умолчанию.
struct X
{
    X(X&& other); // move constructor
//  X(X other);   // Error: incorrect parameter type
};
 
union Y
{
    Y(Y&& other, int num = 1); // move constructor with multiple parameters
//  Y(Y&& other, int num);     // Error: `num` has no default argument
};

Конструктор перемещения обычно вызывается, когда объект инициализируется (с помощью прямой инициализации или копирующей инициализации) из справа от знака равенства (xvalue или prvalue)(до C++17)xvalue(с C++17) того же типа, включая

  • инициализацию: T a = std::move(b); или T a(std::move(b));, где b имеет тип T;
  • передачу аргумента функции: f(std::move(a));, где a имеет тип T и f является void f(T t);
  • возврат функции: return a; внутри функции, такой как T f(), где a имеет тип T, который имеет конструктор перемещения.

Когда инициализатор — это prvalue, вызов конструктора перемещения часто оптимизируется(до C++17)никогда не происходит(с C++17), см. исключение копирования.

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

Неявный конструктор перемещения

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

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

Тогда компилятор объявит конструктор перемещения как не-явный inline public член своего класса с сигнатурой T::T(T&&).

Класс может иметь несколько конструкторов перемещения, например, как T::T(const T&&) и T::T(T&&). Если присутствуют некоторые пользовательские конструкторы перемещения, пользователь все еще может принудить к генерации неявного конструктора перемещения с помощью ключевого слова default.

Неявный (или заданный по умолчанию при первом объявлении) конструктор перемещения имеет спецификацию исключений, как описано в динамической спецификации исключений(до C++17)спецификации noexcept(с C++17).

Неявно определенный конструктор перемещения

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

Если это удовлетворяет требованиям constexpr-конструктора(до C++23)конструктора функции(с C++23), сгенерированный конструктор перемещения является constexpr.

Удаленный конструктор перемещения

Неявно объявленный или явным образом заданный по умолчанию конструктор перемещения для класса T определен как удаленный, если T имеет подлежащий построению подобъект типа класса M (или, возможно, многомерный массив такого типа), такой что

  • M имеет деструктор, который удален или недоступен из конструктора копирования, или
  • процесс разрешения перегрузки для поиска конструктора перемещения M
    • не приводит к пригодному кандидату, или
    • в случае, когда подобъект является членом-вариантом, выбирает нетривиальную функцию.

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

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

Конструктор перемещения для класса T тривиален, если все нижеследующее верно:

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

Тривиальный конструктор перемещения — это конструктор, который выполняет ту же операцию, что и тривиальный конструктор копирования, то есть создаёт копию представления объекта так, как если бы std::memmove. Все типы данных, совместимые с языком C (POD-типы), тривиально перемещаемы.

Допустимый конструктор перемещения

Конструктор перемещения допустим, если он не удалён.

(до C++20)

Конструктор перемещения допустим, если

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

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

Примечания

Для возможности сильной гарантии исключений пользовательские конструкторы перемещения не должны выбрасывать исключения. Например, std::vector полагается на std::move_if_noexcept для выбора между перемещением и копированием, когда элементы необходимо переместить.

Если оба конструктора копирования и перемещения предоставлены и никакие другие конструкторы не являются жизнеспособными, разрешение перегрузки выбирает конструктор перемещения, если аргумент является ссылкой на правое значение того же типа (ссылка на правое значение, например, результат std::move или ссылка на правое значение, например, безымянная временная переменная(до C++17)), и выбирает конструктор копирования, если аргумент является ссылкой на левое значение (именованный объект или функция/оператор, возвращающий ссылку на левое значение). Если предоставлен только конструктор копирования, все категории аргументов выбирают его (пока он принимает ссылку на константное значение, так как правые значения могут связываться с ссылками на константы), что делает копирование резервным вариантом при перемещении, если перемещение недоступно.

Конструктор называется конструктором перемещения, когда он принимает ссылку на правое значение в качестве параметра. Он не обязан перемещать что-либо; класс не обязан иметь ресурс для перемещения, и конструктор перемещения может не быть способен перемещать ресурс, как в допустимом (но, возможно, не разумном) случае, когда параметр является константной ссылкой на правое значение (const T&&).

Пример

#include <iomanip>
#include <iostream>
#include <string>
#include <utility>
 
struct A
{
    std::string s;
    int k;
 
    A() : s("test"), k(-1) {}
    A(const A& o) : s(o.s), k(o.k) { std::cout << "move failed!\n"; }
    A(A&& o) noexcept :
        s(std::move(o.s)),       // explicit move of a member of class type
        k(std::exchange(o.k, 0)) // explicit move of a member of non-class type
    {}
};
 
A f(A a)
{
    return a;
}
 
struct B : A
{
    std::string s2;
    int n;
    // implicit move constructor B::(B&&)
    // calls A's move constructor
    // calls s2's move constructor
    // and makes a bitwise copy of n
};
 
struct C : B
{
    ~C() {} // destructor prevents implicit move constructor C::(C&&)
};
 
struct D : B
{
    D() {}
    ~D() {}           // destructor would prevent implicit move constructor D::(D&&)
    D(D&&) = default; // forces a move constructor anyway
};
 
int main()
{
    std::cout << "Trying to move A\n";
    A a1 = f(A()); // return by value move-constructs the target
                   // from the function parameter
 
    std::cout << "Before move, a1.s = " << std::quoted(a1.s)
        << " a1.k = " << a1.k << '\n';
 
    A a2 = std::move(a1); // move-constructs from xvalue
    std::cout << "After move, a1.s = " << std::quoted(a1.s)
        << " a1.k = " << a1.k << '\n';
 
 
    std::cout << "\nTrying to move B\n";
    B b1;
 
    std::cout << "Before move, b1.s = " << std::quoted(b1.s) << "\n";
 
    B b2 = std::move(b1); // calls implicit move constructor
    std::cout << "After move, b1.s = " << std::quoted(b1.s) << "\n";
 
 
    std::cout << "\nTrying to move C\n";
    C c1;
    C c2 = std::move(c1); // calls copy constructor
 
    std::cout << "\nTrying to move D\n";
    D d1;
    D d2 = std::move(d1);
}

Вывод:

Trying to move A
Before move, a1.s = "test" a1.k = -1
After move, a1.s = "" a1.k = 0
 
Trying to move B
Before move, b1.s = "test"
After move, b1.s = ""
 
Trying to move C
move failed!
 
Trying to move D

Отчёты об ошибках

Следующие отчёты об ошибках, меняющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применяется к Поведение, как опубликовано Правильное поведение
CWG 1353 C++11 условия, при которых предустановленные конструкторы перемещения определены как удалённые,
не учитывали многомерные типы массивов
учитывать эти типы
CWG 1402 C++11 предустановленный конструктор перемещения, который вызывал
нетривиальный конструктор копирования, был определён как
удалённый; предустановленный конструктор перемещения, который удалён, всё ещё участвовал в разрешении перегрузки
разрешает вызов такого конструктора
копирования; делает игнорируемым
в разрешении перегрузки
CWG 1491 C++11 предустановленный конструктор перемещения класса с нестатическим элементом данных
типа ссылки на правое значение был определён как удалённый
в этом случае не удалён
CWG 2094 C++11 подчинённый объект volatile сделал предустановленный
конструктор перемещения нетривиальным (CWG 496)
тривиальность не затронута

См. также

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

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

Spec-Zone.ru

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