Spec-Zone.ru › C++

Устранение копирования

При соблюдении определённых критериев создание объекта класса из исходного объекта того же типа (без учёта cv-квалификации) может быть опущено, даже если выбранный конструктор и/или деструктор объекта имеют побочные эффекты. Такое устранение создания объекта называется устранением копирования .

Пояснение

Устранение копирования допускается в следующих случаях (их можно комбинировать, устраняя несколько копирований):

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

При устранении копирования реализация рассматривает источник и цель опущенной инициализации как два разных способа обращения к одному и тому же объекту.

Уничтожение происходит в более поздний из тех моментов, когда без оптимизации были бы уничтожены оба объекта.

(до C++11)

Если первый параметр выбранного конструктора является rvalue-ссылкой на тип объекта, уничтожение этого объекта происходит в тот момент, когда был бы уничтожен целевой объект. В противном случае уничтожение происходит в более поздний из тех моментов, когда без оптимизации были бы уничтожены оба объекта.

(начиная с C++11)

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

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

  • Инициализация возвращаемого объекта в инструкции return, когда операндом является 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
}
Деструктор возвращаемого типа должен быть доступен в точке инструкции return и не должен быть удалён, даже если объект 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
};

Примечание: Это правило не описывает оптимизацию, и Стандарт формально не называет его «устранением копирования» (поскольку ничего не устраняется). Вместо этого спецификация 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

Spec-Zone.ru

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