Пространства имён
Варианты
Действия

Конструкторы перемещения

Материал из cppreference.com
< cpp‎ | language
 
 
 
 

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

Содержание

[править] Синтаксис

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

[править] Объяснение

1) Объявление конструктора перемещения внутри определения класса.
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), смотрите пропуск копирования.

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

[править] Смотрите также