Конструкторы перемещения
Конструктор перемещения — это конструктор, который может быть вызван с аргументом того же типа класса и копирует содержимое аргумента, возможно изменяя сам аргумент.
Синтаксис
class-name (parameter-list ); | (1) | |
class-name (parameter-list ) function-body | (2) | |
class-name (single-parameter-list ) = default; | (3) | |
class-name (parameter-list ) = delete; | (4) | |
class-name ::class-name (parameter-list ) function-body | (5) | |
class-name ::class-name (single-parameter-list ) = default; | (6) |
| class-name | - | класс, конструктор перемещения которого объявляется |
| parameter-list | - | непустой список параметров, удовлетворяющий всем следующим условиям:
|
| single-parameter-list | - | список параметров ровно с одним параметром типа T&&, constT&&, volatileT&& или constvolatileT&&, для которого не задан аргумент по умолчанию |
| function-body | - | тело функции конструктора перемещения |
Пояснение
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
};
Конструктор перемещения обычно вызывается, когда объект инициализируется (посредством прямой инициализации или копирующей инициализации) из 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); см. устранение копирования.
Конструкторы перемещения обычно передают ресурсы, которыми владеет аргумент (например, указатели на динамически выделенные объекты, файловые дескрипторы, TCP-сокеты, дескрипторы потоков и т. д.), вместо того чтобы копировать их, и оставляют аргумент в некотором допустимом, но в остальном неопределённом состоянии. Поскольку конструктор перемещения не изменяет время жизни аргумента, его деструктор обычно будет вызван позднее. Например, после перемещения из std::string или из std::vector аргумент может оказаться пустым. Для некоторых типов, таких как std::unique_ptr, состояние после перемещения полностью определено.
Неявно объявленный конструктор перемещения
Если для типа класса не предоставлены пользовательские конструкторы перемещения и выполняются все следующие условия:
- нет пользовательских объявлений конструкторов копирования;
- нет пользовательских объявлений операторов присваивания копированием;
- нет пользовательских объявлений операторов присваивания перемещением;
- нет пользовательского объявления деструктора.
Тогда компилятор объявит конструктор перемещения как не-explicit член класса с модификаторами inlinepublic и сигнатурой T::T(T&&).
У класса может быть несколько конструкторов перемещения, например T::T(const T&&) и T::T(T&&). Если имеются пользовательские конструкторы перемещения, пользователь всё ещё может принудительно сгенерировать неявно объявленный конструктор перемещения с помощью ключевого слова default.
Неявно объявленный (или default-объявленный при первом объявлении) конструктор перемещения имеет спецификацию исключений, описанную в разделе динамическая спецификация исключений(до C++17)спецификация noexcept(начиная с C++17).
Неявно определённый конструктор перемещения
Если неявно объявленный конструктор перемещения не удалён и не является тривиальным, компилятор определяет его (то есть генерирует и компилирует тело функции) при использовании по правилу ODR или если он необходим для константного вычисления. Для типов объединений неявно определённый конструктор перемещения копирует представление объекта (как при std::memmove). Для типов классов, не являющихся объединениями, конструктор перемещения выполняет полное поэлементное перемещение прямых базовых подобъектов и подобъектов-членов объекта в порядке их инициализации, используя прямую инициализацию с аргументом xvalue. Для каждого нестатического члена-данных ссылочного типа конструктор перемещения привязывает ссылку к тому же объекту или функции, к которым привязана исходная ссылка.
Если это удовлетворяет требованиям к конструктору constexpr(до C++23)функции constexpr(начиная с C++23), сгенерированный конструктор перемещения является constexpr.
Удалённый конструктор перемещения
Неявно объявленный или явно default-объявленный конструктор перемещения класса T определяется как удалённый, если T имеет потенциально конструируемый подобъект типа класса M (или, возможно, многомерный массив такого типа), для которого выполняется одно из следующих условий:
-
Mимеет деструктор, который удалён или недоступен из конструктора копирования; либо - разрешение перегрузки при поиске конструктора перемещения для
M - не находит подходящего кандидата; либо
- если подобъект является вариативным членом, выбирает нетривиальную функцию.
Такой конструктор игнорируется при разрешении перегрузки (иначе он препятствовал бы копирующей инициализации из rvalue).
Тривиальный конструктор перемещения
Конструктор перемещения класса T является тривиальным, если выполняются все следующие условия:
- он не предоставлен пользователем (то есть определён неявно или объявлен со значением по умолчанию);
-
Tне имеет виртуальных функций-членов; -
Tне имеет виртуальных базовых классов; - конструктор перемещения, выбранный для каждой прямой базовой части
T, является тривиальным; - конструктор перемещения, выбранный для каждого нестатического члена
Tтипа класса (или массива типа класса), является тривиальным.
Тривиальный конструктор перемещения — это конструктор, выполняющий то же действие, что и тривиальный конструктор копирования, то есть копирующий представление объекта, как если бы использовался std::memmove. Все типы данных, совместимые с языком C, допускают тривиальное перемещение.
Подходящий конструктор перемещения
| Конструктор перемещения является подходящим, если он не удалён. | (до C++20) |
| Конструктор перемещения является подходящим, если выполняются все следующие условия:
| (начиная с C++20) |
Тривиальность подходящих конструкторов перемещения определяет, является ли класс типом с неявным временем жизни и является ли класс тривиально копируемым типом.
Примечания
Чтобы обеспечить строгую гарантию безопасности при исключениях, пользовательские конструкторы перемещения не должны выбрасывать исключения. Например, std::vector использует std::move_if_noexcept для выбора между перемещением и копированием при необходимости переместить элементы.
Если предоставлены и конструкторы копирования, и конструкторы перемещения, а других подходящих конструкторов нет, разрешение перегрузки выбирает конструктор перемещения, если аргумент является rvalue того же типа (xvalue, например результат std::move или prvalue, например безымянный временный объект(до C++17)), и выбирает конструктор копирования, если аргумент является lvalue (именованный объект или функция/оператор, возвращающие lvalue-ссылку). Если предоставлен только конструктор копирования, все категории аргументов выбирают его (при условии, что он принимает ссылку на const, поскольку 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)), // 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 | условия, при которых default-объявленные конструкторы перемещения определялись как удалённые, не учитывали многомерные массивы | учитывать такие типы |
| CWG 1402 | C++11 | default-объявленный конструктор перемещения, который вызывал бы нетривиальный конструктор копирования, определялся как удалённый; удалённый default-объявленный конструктор перемещения всё ещё участвовал в разрешении перегрузки | разрешён вызов такого конструктора копирования; конструктор исключён из разрешения перегрузки |
| CWG 1491 | C++11 | default-объявленный конструктор перемещения класса с нестатическим членом-данными типа rvalue-ссылки определялся как удалённый | в этом случае не удаляется |
| CWG 2094 | C++11 | volatile-подобъект делал default-объявленный конструктор перемещения нетривиальным (отчёт CWG 496) | тривиальность не затрагивается |
| CWG 2595 | C++20 | конструктор перемещения не считался подходящим, если существует другой конструктор перемещения с более строгими ограничениями, но его связанные ограничения не удовлетворены | в этом случае он может быть подходящим |
См. также
- преобразующий конструктор
- присваивание копированием
- конструктор копирования
- устранение копирования
- конструктор по умолчанию
- деструктор
explicit- инициализация
- присваивание перемещением
new
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/move_constructor