Конструкторы перемещения
Конструктор перемещения это конструктор, который можно вызвать с аргументом того же классового типа и копировать содержимое аргумента, возможно, изменяя аргумент.
[править] Синтаксис
имя-класса ( список-параметров );
|
(1) | ||||||||
имя-класса ( список-параметров ) тело-функции
|
(2) | ||||||||
имя-класса ( список-одного-параметра ) = default;
|
(3) | ||||||||
имя-класса ( список-параметров ) = delete;
|
(4) | ||||||||
имя-класса :: имя-класса ( список-параметров ) тело-функции
|
(5) | ||||||||
имя-класса :: имя-класса ( список-одного-параметра ) = default;
|
(6) | ||||||||
имя-класса | — | класс, конструктор перемещения которого объявляется |
список-параметров | — | непустой список параметров, соответствующий всем следующим условиям:
|
список-одного-параметра | — | список параметров только из одного параметра, который имеет тип T&&, const T&&, volatile T&& или const volatile T&& и не имеет аргумента по умолчанию |
тело-функции | — | тело функции конструктора перемещения |
[править] Объяснение
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), смотрите пропуск копирования.
Конструкторы перемещения обычно "крадут" ресурсы, содержащиеся в аргументе (например, указатели на динамически размещаемые объекты, дескрипторы файлов, сокеты TCP, потоки ввода-вывода, запущенные потоки и т.д.), а не делают их копии и оставляют аргумент в каком-то действительном, но в остальном неопределённом состоянии. Например, перемещение из std::string или std::vector может привести к тому, что аргумент останется пустым. Однако на такое поведение не следует полагаться. Для некоторых типов, таких как std::unique_ptr, полностью определено состояние перемещения.
[править] Неявно объявленный конструктор перемещения
Если для классового типа (struct, class или union) не предоставлены пользовательские конструкторы перемещения, и выполняются все следующие условия:
- нет объявленных пользователем конструкторов копирования;
- нет объявленных пользователем операторов присваивания копированием;
- нет объявленных пользователем операторов присваивания перемещением;
- нет объявленного пользователем деструктора.
Тогда компилятор объявляет конструктор перемещения как не explicit inline public
элемент своего класса с сигнатурой T::T(T&&)
.
Класс может иметь несколько конструкторов перемещения, например, как T::T(const T&&), так и T::T(T&&). Если присутствуют некоторые определяемые пользователем конструкторы перемещения, пользователь всё равно может принудительно сгенерировать неявно объявленный конструктор перемещения ключевым словом default
.
Неявно объявленный (или заданный по умолчанию при первом объявлении) конструктор перемещения имеет спецификацию исключения, как описано в спецификации динамических исключений (до C++17)спецификации noexcept (начиная с C++17)
[править] Неявно определённый конструктор перемещения
Если неявно объявленный конструктор перемещения не является ни удалённым, ни тривиальным, он определяется (то есть тело функции генерируется и компилируется) компилятором, если используется odr или нуждается в константной оценке. Для типов union неявно определённый конструктор перемещения копирует представление объекта (как std::memmove). Для классовых типов не объединений (class и struct) конструктор перемещения выполняет полное перемещение по элементам базовых классов объекта и нестатических элементов в порядке их инициализации с использованием прямой инициализации аргументом xvalue.
Если это соответствует требованиям констурктора constexpr (до C++23)функции конструктора (начиная с C++23), сгенерированный конструктор перемещения является constexpr.
[править] Удалённый неявно объявленный конструктор перемещения
Неявно объявленный или заданный по умолчанию конструктор перемещения для класса T
определяется как удалённый, если выполняется любое из следующих условий:
-
T
имеет нестатические элементы данных, которые нельзя перемещать (имеют удалённые, недоступные или неоднозначные конструкторы перемещения); -
T
имеет прямой или виртуальный базовый класс, который нельзя переместить (имеет удалённые, недоступные или неоднозначные конструкторы перемещения); -
T
имеет прямой или виртуальный базовый класс или нестатический элемент данных с удалённым или недоступным деструктором; -
T
является классом, подобным объединению, и имеет вариантный элемент с нетривиальным конструктором перемещения.
Удаляемый конструктор перемещения по умолчанию игнорируется разрешением перегрузки (в противном случае это предотвратит инициализацию копированием из rvalue).
[править] Тривиальный конструктор перемещения
Конструктор перемещения для класса T
тривиален, если выполняются все следующие условия:
- он не предоставляется пользователем (это означает, что он неявно определён или установлен по умолчанию);
-
T
не имеет виртуальных функций-элементов; -
T
не имеет виртуальных базовых классов; - конструктор перемещения, выбранный для каждого прямого базового класса класса
T
, тривиален; - конструктор перемещения, выбранный для каждого элемента
T
нестатического классового типа (или массива классового типа), является тривиальным.
Тривиальный конструктор перемещения это конструктор, который выполняет то же действие, что и тривиальный конструктор копирования, т.е. создаёт копию представления объекта, как будто с помощью std::memmove. Все типы данных, совместимые с языком C (типы POD), перемещаются тривиально.
[править] Доступный конструктор перемещения
Конструктор перемещения доступен, если он не удалён. |
(до C++20) |
Конструктор перемещения доступен, если
|
(начиная с C++20) |
Тривиальность доступных конструкторов перемещения определяет, является ли класс типом с неявным временем жизни и является ли класс типом, который можно копировать тривиально.
[править] Примечание
Чтобы сделать возможной строгую гарантию исключений, пользовательские конструкторы перемещения не должны генерировать исключения. Например, std::vector использует std::move_if_noexcept для выбора между перемещением и копированием, когда элементы необходимо переместить.
Если предоставлены конструкторы копирования и перемещения и никакие другие конструкторы не являются жизнеспособными, разрешение перегрузки выбирает конструктор перемещения, если аргумент является rvalue того же типа (xvalue, такое же как результат std::move или значение prvalue, такое же как безымянное временное значение (до C++17)), и выбирает конструктор копирования, если аргумент является lvalue (именованный объект или функция/оператор, возвращающий ссылку на lvalue). Если предоставляется только конструктор копирования, все категории аргументов выбирают его (до тех пор, пока он принимает ссылку на const, поскольку значения rvalue могут связываться с ссылками на const), что делает копирование запасным вариантом для перемещения, когда перемещение недоступно.
Конструктор называется конструктором перемещения, если он принимает ссылку rvalue в качестве параметра. Он не обязан что-либо перемещать, классу не требуется иметь ресурс для перемещения, и 'конструктор перемещения' может не иметь возможности перемещать ресурс, как в допустимом (но, возможно, неразумном) случае, когда параметр является ссылкой на константное значение (const T&&
).
[править] Пример
#include <iomanip> #include <iostream> #include <string> #include <utility> struct A { std::string s; int k; A() : s("тест"), k(-1) {} A(const A& o) : s(o.s), k(o.k) { std::cout << "перемещение не удалось!\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 << "Пытаюсь переместить A\n"; A a1 = f(A()); // возвращает по значению сконструированную перемещением цель // из параметра функции std::cout << "Перед перемещением, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; A a2 = std::move(a1); // конструирование перемещением из xvalue std::cout << "После перемещения, a1.s = " << std::quoted(a1.s) << " a1.k = " << a1.k << '\n'; std::cout << "Пытаюсь переместить B\n"; B b1; std::cout << "Перед перемещением, b1.s = " << std::quoted(b1.s) << "\n"; B b2 = std::move(b1); // вызывает неявный конструктор перемещения std::cout << "После перемещения, b1.s = " << std::quoted(b1.s) << "\n"; std::cout << "Пытаюсь переместить C\n"; C c1; C c2 = std::move(c1); // вызывает конструктор копирования std::cout << "Пытаюсь переместить D\n"; D d1; D d2 = std::move(d1); }
Вывод:
Пытаюсь переместить A Перед перемещением, a1.s = "тест" a1.k = -1 После перемещения, a1.s = "" a1.k = 0 Пытаюсь переместить B Перед перемещением, b1.s = "тест" После перемещения, b1.s = "" Пытаюсь переместить C перемещение не удалось! Пытаюсь переместить D
[править] Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 1402 | C++11 | конструктор перемещения по умолчанию, который вызывал бы нетривиальный конструктор копирования, был определён как удалённый; конструктор перемещения по умолчанию, который удалён, все еще участвовал в разрешении перегрузки |
разрешает вызов такого конструктора копирования; сделан игнорируемым в разрешении перегрузки |
CWG 1491 | C++11 | конструктор перемещения по умолчанию класса с нестатическим элементом данных ссылочного типа rvalue был определён как удалённый |
не удаляется в данном случае |
CWG 2094 | C++11 | volatile подобъект делает конструктор перемещения по умолчанию нетривиальным (CWG 496) |
тривиальность не затрагивается |