Исключение копирования
Исключает конструкторы копирования и перемещения(с C++11), что приводит к семантике передачи по значению без копирования.
Объяснение
Семантика prvalue («гарантированное исключение копирования»)Начиная с C++17, prvalue не материализуется до тех пор, пока это не потребуется, и затем он строится непосредственно в хранилище своего конечного назначения. Это иногда означает, что даже когда синтаксис языка визуально предполагает копирование/перемещение (например, инициализация копированием), копирование/перемещение не выполняется — это означает, что тип вообще не должен иметь доступного конструктора копирования/перемещения. Примеры включают:
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 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, «оптимизация именованного возвращаемого значения».
| (до C++17) |
| URVO является обязательным и больше не рассматривается как форма исключения копирования; см. выше. | (с C++17) |
| (с 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 | деструктор потенциально вызывается |
См. также
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/copy_elision