Устранение копирования
При соблюдении определённых критериев создание объекта класса из исходного объекта того же типа (без учёта cv-квалификации) может быть опущено, даже если выбранный конструктор и/или деструктор объекта имеют побочные эффекты. Такое устранение создания объекта называется устранением копирования .
Пояснение
Устранение копирования допускается в следующих случаях (их можно комбинировать, устраняя несколько копирований):
- В return инструкции в функции с возвращаемым типом класса, когда операндом является имя невольтильного объекта
objс автоматической длительностью хранения (кроме параметра функции или параметра обработчика), копирующая инициализация результирующего объекта может быть опущена путём непосредственного созданияobjв результирующем объекте вызова функции. Этот вариант устранения копирования известен как оптимизация именованного возвращаемого значения (NRVO).
| (до C++17) |
| (начиная с C++11) |
| (начиная с C++20) |
При устранении копирования реализация рассматривает источник и цель опущенной инициализации как два разных способа обращения к одному и тому же объекту.
| Уничтожение происходит в более поздний из тех моментов, когда без оптимизации были бы уничтожены оба объекта. | (до C++11) |
| Если первый параметр выбранного конструктора является rvalue-ссылкой на тип объекта, уничтожение этого объекта происходит в тот момент, когда был бы уничтожен целевой объект. В противном случае уничтожение происходит в более поздний из тех моментов, когда без оптимизации были бы уничтожены оба объекта. | (начиная с 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
}
Деструктор возвращаемого типа должен быть доступен в точке инструкции return и не должен быть удалён, даже если объект T не уничтожается.
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
};
Примечание: Это правило не описывает оптимизацию, и Стандарт формально не называет его «устранением копирования» (поскольку ничего не устраняется). Вместо этого спецификация prvalue и временных объектов в основном языке C++17 принципиально отличается от спецификаций предыдущих редакций C++: больше нет временного объекта, из которого выполнялось бы копирование/перемещение. Механизм C++17 можно также описать как «передачу нематериализованных значений» или «отложенную материализацию временных объектов»: prvalue возвращаются и используются, так и не материализуясь во временный объект. | (начиная с C++17) |
Примечания
Устранение копирования — единственная разрешённая форма оптимизации(до C++14) одна из двух разрешённых форм оптимизации наряду с устранением и расширением выделения памяти,(начиная с C++14), которая может изменять наблюдаемые побочные эффекты. Поскольку некоторые компиляторы не выполняют устранение копирования во всех разрешённых случаях (например, в режиме отладки), программы, зависящие от побочных эффектов конструкторов копирования/перемещения и деструкторов, непереносимы.
| В инструкции return или выражении throw, если компилятор не может выполнить устранение копирования, но условия для него соблюдены (или были бы соблюдены, если бы источник не являлся параметром функции), компилятор попытается использовать конструктор перемещения, даже если исходный операнд обозначен lvalue(до C++23) исходный операнд будет рассматриваться как rvalue(начиная с C++23); подробности см. в статье «Инструкция return». В константных выражениях и при константной инициализации устранение копирования никогда не выполняется. 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) |
| Макрос проверки поддержки возможности | Значение | Стандарт | Возможность |
|---|---|---|---|
__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 2426 | C++17 | деструктор не требовался при возврате prvalue | деструктор может быть вызван |
| CWG 2930 | C++98 | могли быть устранены только операции копирования(/перемещения), но при копирующей инициализации мог быть выбран конструктор, не являющийся конструктором копирования(/перемещения) | устраняется создание любого объекта при соответствующей копирующей инициализации |
См. также
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/copy_elision