Namespaces
Variants

Move constructors

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

Содержание

Синтаксис

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

Объяснение

1) Объявление move-конструктора внутри определения класса.
2-4) Определение конструктора перемещения внутри определения класса.
3) Конструктор перемещения явно задан по умолчанию.
4) Конструктор перемещения удалён.
5,6) Определение перемещающего конструктора вне определения класса (класс должен содержать объявление (1) ).
6) Конструктор перемещения явно задан по умолчанию.
struct X
{
    X(X&& other); // перемещающий конструктор
//  X(X other);   // Ошибка: неверный тип параметра
};
union Y
{
    Y(Y&& other, int num = 1); // перемещающий конструктор с несколькими параметрами
//  Y(Y&& other, int num);     // Ошибка: у `num` отсутствует аргумент по умолчанию
};

Конструктор перемещения обычно вызывается, когда объект инициализируется (с помощью прямой инициализации или копирующей инициализации ) из rvalue (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) , см. copy elision .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Подходящий move constructor

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

(до C++20)

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

(начиная с C++20)

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

Примечания

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

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

Пример

#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)),       // явное перемещение члена типа класса
        k(std::exchange(o.k, 0)) // явное перемещение члена неклассового типа
    {}
};
A f(A a)
{
    return a;
}
struct B : A
{
    std::string s2;
    int n;
    // неявный конструктор перемещения B::(B&&)
    // вызывает конструктор перемещения A
    // вызывает конструктор перемещения s2
    // и выполняет побитовое копирование n
};
struct C : B
{
    ~C() {} // деструктор предотвращает неявный конструктор перемещения C::(C&&)
};
struct D : B
{
    D() {}
    ~D() {}           // деструктор предотвратил бы неявный конструктор перемещения D::(D&&)
    D(D&&) = default; // принудительно создает конструктор перемещения
};
int main()
{
    std::cout << "Trying to move A\n";
    A a1 = f(A()); // возврат по значению перемещает целевой объект
                   // из параметра функции
    std::cout << "Before move, a1.s = " << std::quoted(a1.s)
        << " a1.k = " << a1.k << '\n';
    A a2 = std::move(a1); // перемещает из 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); // вызывает неявный конструктор перемещения
    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); // вызывает конструктор копирования
    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 перемещающий конструктор по умолчанию для класса с нестатическим
членом данных типа rvalue-ссылки определялся как удалённый
не удаляется в этом случае
CWG 2094 C++11 volatile подобъект делал перемещающий конструктор
по умолчанию нетривиальным ( CWG issue 496 )
тривиальность не затрагивается
CWG 2595 C++20 перемещающий конструктор не был доступен, если существует
другой перемещающий конструктор, который более ограничен,
но не удовлетворяет своим ассоциированным ограничениям
может быть доступен в этом случае

Смотрите также