Spec-Zone.ru › C++

Исключение копирования

Исключает конструкторы копирования и перемещения(с C++11), что приводит к семантике передачи по значению без копирования.

Объяснение

Семантика prvalue («гарантированное исключение копирования»)

Начиная с C++17, prvalue не материализуется до тех пор, пока это не потребуется, и затем он строится непосредственно в хранилище своего конечного назначения. Это иногда означает, что даже когда синтаксис языка визуально предполагает копирование/перемещение (например, инициализация копированием), копирование/перемещение не выполняется — это означает, что тип вообще не должен иметь доступного конструктора копирования/перемещения. Примеры включают:

  • Инициализация возвращаемого объекта в операторе возврата, когда операнд является prvalue того же типа класса (не учитывая cv-квалификацию) что и тип возвращаемого значения функции:
T f()
{
    return U(); // constructs a temporary of type U,
                // then initializes the returned T from the temporary
}
T g()
{
    return T(); // constructs the returned T directly; no move
}
Деструктор возвращаемого типа должен быть доступен в момент оператора возврата и не удалён, даже если объект T не уничтожается.
  • При инициализации объекта, когда выражение инициализатора является prvalue того же типа класса (не учитывая cv-квалификацию) что и тип переменной:
T x = T(T(f())); // x is initialized by the result of f() directly; no move
Это может применяться только тогда, когда объект, который инициализируется, известен как не потенциально перекрывающийся подобъект:
struct C { /* ... */ };
C f();
 
struct D;
D g();
 
struct D : C
{
    D() : C(f()) {}    // no elision when initializing a base-class subobject
    D(int) : D(g()) {} // no elision because the D object being initialized might
                       // be a base-class subobject of some other class
};

Примечание: Это правило не определяет оптимизацию, и Стандарт не формально описывает его как «исключение копирования» (потому что ничего не исключается). Вместо этого, спецификация ядра языка C++17 prvalue и временных объектов фундаментально отличается от предыдущих версий C++: больше нет временного объекта для копирования/перемещения. Другой способ описать механику C++17 — это «передача значения без материализации» или «отложенная материализация временных объектов»: prvalue возвращаются и используются без материализации временного объекта.

(с C++17)

Необязательное исключение копирования/перемещения (с C++11)

В следующих ситуациях компиляторы могут, но не обязаны, исключить построение объектов класса посредством копирования и перемещения(с C++11), даже если конструктор копирования/перемещения и деструктор имеют наблюдаемые побочные эффекты. Объекты создаются непосредственно в хранилище, куда они иначе копировались/перемещались. Это оптимизация: даже когда она происходит и конструктор копирования/перемещения не вызывается, он всё ещё должен быть присутствующим и доступным (как если бы никакой оптимизации не было), иначе программа является ошибочной:

  • В операторе возврата, когда операнд — имя не-постоянного объекта с автоматической областью хранения, который не является параметром функции или параметром блока обработки исключений, и который имеет тот же тип класса (не учитывая cv-квалификацию) что и тип возвращаемого значения функции. Этот вариант исключения копирования известен как NRVO, «оптимизация именованного возвращаемого значения».
  • При инициализации объекта, когда исходный объект — безымянный временный объект, и он имеет тот же тип класса (не учитывая cv-квалификацию) что и целевой объект. Когда безымянный временный объект является операндом оператора возврата, этот вариант исключения копирования известен как URVO, «оптимизация безымянного возвращаемого значения».
(до C++17)

URVO является обязательным и больше не рассматривается как форма исключения копирования; см. выше.

(с C++17)
  • В выражении throw, когда операнд — имя не-постоянного объекта с автоматической областью хранения, который не является параметром функции или параметром блока обработки исключений, и чья область действия не выходит за пределы самого внутреннего блока try (если есть блок try).
  • В блоке обработки исключения, когда аргумент имеет тот же тип (не учитывая cv-квалификацию) что и объект исключения, брошенный исключение, копия объекта исключения исключается, и тело блока обработки исключений обращается к объекту исключения напрямую, как если бы он был получен по ссылке (перемещение из объекта исключения невозможно, так как он всегда является lvalue). Это отключено, если такое исключение копирования изменит наблюдаемое поведение программы по какой-либо причине, отличной от пропуска конструктора и деструктора аргумента блока обработки исключений (например, если аргумент блока обработки исключений изменяется, и объект исключения перебрасывается с throw).
(с C++11)
  • В корутинах, копирование/перемещение параметра в состояние корутины может быть исключено, где это не меняет поведения программы, кроме как путём пропуска вызовов конструктора и деструктора параметра. Это может произойти, если параметр никогда не ссылается после точки приостановки или когда всё состояние корутины никогда не было выделено в куче в первую очередь.
(с C++20)

Когда происходит исключение копирования, реализация рассматривает исходный и целевой объекты исключённой операции копирования/перемещения просто как два разных способа ссылки на один и тот же объект, и уничтожение этого объекта происходит в более поздний момент, чем если бы оптимизация не производилась (за исключением того, что, если параметр выбранного конструктора является ссылкой rvalue на тип объекта, уничтожение происходит, когда целевой объект был бы уничтожен)(с C++11).

Несколько исключений копирования могут быть объединены для исключения нескольких копий.

  • В постоянных выражениях и постоянной инициализации исключение копирования никогда не выполняется.
struct A
{
    void* p;
    constexpr A() : p(this) {}
    A(const A&); // Disable trivial copyability
};
 
constexpr A a;  // OK: a.p points to a
 
constexpr A f()
{
    A x;
    return x;
}
constexpr A b = f(); // error: b.p would be dangling and point to the x inside f
 
constexpr A c = A(); // (until C++17) error: c.p would be dangling and point to a temporary
                     // (since C++17) OK: c.p points to c; no temporary is involved
(с C++11)

Примечания

Исключение копирования — единственная разрешённая форма оптимизации(до C++14) одна из двух разрешённых форм оптимизации, наряду с исключением и расширением выделения,(с C++14) которые могут изменять наблюдаемые побочные эффекты. Поскольку некоторые компиляторы не выполняют исключение копирования во всех ситуациях, где это разрешено (например, в режиме отладки), программы, которые полагаются на побочные эффекты конструкторов и деструкторов копирования/перемещения, не являются переносимыми.

В операторе возврата или выражении throw, если компилятор не может выполнить исключение копирования, но условия для исключения копирования соблюдены или были бы соблюдены, за исключением того, что источник является параметром функции, компилятор попытается использовать конструктор перемещения, даже если исходный операнд обозначен lvalue(до C++23) исходный операнд будет рассматриваться как rvalue(с C++23); см. оператор возврата для получения подробностей.

(с C++11)
Макрос проверки функций Значение Std Функция
__cpp_guaranteed_copy_elision 201606L (C++17) Гарантированное исключение копирования с помощью упрощенных категорий значений

Пример

#include <iostream>
 
struct Noisy
{
    Noisy() { std::cout << "constructed at " << this << '\n'; }
    Noisy(const Noisy&) { std::cout << "copy-constructed\n"; }
    Noisy(Noisy&&) { std::cout << "move-constructed\n"; }
    ~Noisy() { std::cout << "destructed at " << this << '\n'; }
};
 
Noisy f()
{
    Noisy v = Noisy(); // (until C++17) copy elision initializing v from a temporary;
                       //               the move constructor may be called
                       // (since C++17) "guaranteed copy elision"
    return v; // copy elision ("NRVO") from v to the result object;
              // the move constructor may be called
}
 
void g(Noisy arg)
{
    std::cout << "&arg = " << &arg << '\n';
}
 
int main()
{
    Noisy v = f(); // (until C++17) copy elision initializing v from the result of f()
                   // (since C++17) "guaranteed copy elision"
 
    std::cout << "&v = " << &v << '\n';
 
    g(f()); // (until C++17) copy elision initializing arg from the result of f()
            // (since C++17) "guaranteed copy elision"
}

Возможный вывод:

constructed at 0x7fffd635fd4e
&v = 0x7fffd635fd4e
constructed at 0x7fffd635fd4f
&arg = 0x7fffd635fd4f
destructed at 0x7fffd635fd4f
destructed at 0x7fffd635fd4e

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применимо к Поведение, как опубликовано Правильное поведение
CWG 1967 C++11 при исключении копирования с использованием конструктора перемещения,
жизненный цикл перемещаемого объекта всё ещё рассматривался
не рассматривался
CWG 2022 C++11 исключение копирования было необязательным во время вычисления констант обязательным во время вычисления констант
CWG 2278 C++11 исключение копирования было обязательным во время вычисления констант запрещено во время вычисления констант
CWG 2426 C++17 деструктор не требуется при возвращении prvalue деструктор потенциально вызывается
END_OF_DOCUMENT_MARKER

См. также

  • копирующее инициализирование
  • копирующий конструктор
  • конструктор перемещения

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/copy_elision

Spec-Zone.ru

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