Spec-Zone.ru › C++

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

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

Синтаксис

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 - непустой список параметров, удовлетворяющий всем следующим условиям:
  • если тип класса обозначить как T, первый параметр имеет тип T&&, constT&&, volatileT&& или constvolatileT&&, и
  • либо других параметров нет, либо для всех остальных параметров заданы аргументы по умолчанию
single-parameter-list - список параметров ровно с одним параметром типа T&&, constT&&, volatileT&& или constvolatileT&&, для которого не задан аргумент по умолчанию
function-body - тело функции конструктора перемещения

Пояснение

1) Объявление конструктора перемещения внутри определения класса.
2-4) Определение конструктора перемещения внутри определения класса.
3) Конструктор перемещения явно default-объявлен.
4) Конструктор перемещения удалён.
5,6) Определение конструктора перемещения вне определения класса (класс должен содержать объявление (1)).
6) Конструктор перемещения явно default-объявлен.
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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API