Move constructors
Конструктор перемещения — это конструктор , который может быть вызван с аргументом того же типа класса и копирует содержимое аргумента, возможно, изменяя аргумент.
Содержание |
Синтаксис
имя-класса
(
список-параметров
);
|
(1) | ||||||||
имя-класса
(
список-параметров
)
тело-функции
|
(2) | ||||||||
имя-класса
(
список-одиночных-параметров
) = default;
|
(3) | ||||||||
имя-класса
(
список-параметров
) = delete;
|
(4) | ||||||||
имя-класса
::
имя-класса
(
список-параметров
)
тело-функции
|
(5) | ||||||||
имя-класса
::
имя-класса
(
список-одиночных-параметров
) = default;
|
(6) | ||||||||
| class-name | - | класс, для которого объявляется перемещающий конструктор |
| parameter-list | - |
непустой
список параметров
, удовлетворяющий всем следующим условиям:
|
| single-parameter-list | - | список параметров только из одного параметра, который имеет тип T && , const T && , volatile T && или const volatile T && и не имеет аргумента по умолчанию |
| function-body | - | тело функции перемещающего конструктора |
Объяснение
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 , состояние после перемещения полностью определено.
Неявно объявленный конструктор перемещения
Если для типа класса не определены пользовательские конструкторы перемещения и выполняются все следующие условия:
- нет пользовательских copy constructors ;
- нет пользовательских copy assignment operators ;
- нет пользовательских move assignment operators ;
- нет пользовательского destructor .
Тогда компилятор объявит конструктор перемещения как не- 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 |
перемещающий конструктор не был доступен, если существует
другой перемещающий конструктор, который более ограничен, но не удовлетворяет своим ассоциированным ограничениям |
может быть доступен в этом случае |