Конструкторы перемещения
Конструктор перемещения — это конструктор, который может быть вызван с аргументом того же типа класса и копирует содержимое аргумента, возможно, изменяя аргумент.
Синтаксис
имя-класса (список-параметров ); | (1) | |
имя-класса (список-параметров ) тело-функции | (2) | |
имя-класса (список-с-одним-параметром ) = default; | (3) | |
имя-класса (список-параметров ) = delete; | (4) | |
имя-класса ::имя-класса (список-параметров ) тело-функции | (5) | |
имя-класса ::имя-класса (список-с-одним-параметром ) = default; | (6) |
| имя-класса | - | класс, для которого объявляется конструктор перемещения |
| список-параметров | - | непустой список параметров, удовлетворяющий всем следующим условиям:
|
| список-с-одним-параметром | - | список параметров только с одним параметром, который имеет тип T&&, const T&&, volatile T&& или const volatile T&& и не имеет значения по умолчанию |
| тело-функции | - | тело функции конструктора перемещения |
Объяснение
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является тривиальным.
Тривиальный конструктор перемещения — это конструктор, который выполняет ту же операцию, что и тривиальный конструктор копирования, то есть создаёт копию представления объекта так, как если бы 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