Namespaces
Variants

Copy elision

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Wenn bestimmte Kriterien erfüllt sind, kann die Erstellung eines Klassenobjekts aus einem Quellobjekt desselben Typs (unter Vernachlässigung von cv-Qualifikationen) entfallen, selbst wenn der ausgewählte Konstruktor und/oder der Destruktor für das Objekt Nebeneffekte haben. Diese Auslassung der Objekterstellung wird Copy Elision  genannt.

Inhaltsverzeichnis

Erklärung

Copy Elision ist in den folgenden Situationen zulässig (die kombiniert werden können, um mehrere Kopien zu eliminieren):

  • In einer return -Anweisung in einer Funktion mit einem Klassenrückgabetyp, wenn der Operand der Name eines nicht-flüchtigen Objekts obj mit automatischer Speicherdauer (außer einem Funktionsparameter oder einem Handler-Parameter ), kann die Kopierinitialisierung des Ergebnisobjekts weggelassen werden, indem obj direkt in das Ergebnisobjekt des Funktionsaufrufs konstruiert wird. Diese Variante der Kopierauslassung wird als Named Return Value Optimization (NRVO) bezeichnet.
  • Wenn ein Klassenobjekt target mit einem temporären Klassenobjekt obj kopierinitialisiert wird, das nicht an eine Referenz gebunden wurde, kann die Kopierinitialisierung weggelassen werden, indem obj direkt in target konstruiert wird. Diese Variante des Copy Elision wird als Unnamed Return Value Optimization (URVO) bezeichnet. Seit C++17 ist URVO obligatorisch und wird nicht mehr als Form des Copy Elision betrachtet; siehe unten.
(bis C++17)
  • In einem throw -Ausdruck , wenn der Operand der Name eines nicht-flüchtigen Objekts obj mit automatischer Speicherdauer (außer einem Funktionsparameter oder einem Handler-Parameter) ist, das zu einem Gültigkeitsbereich gehört, der den innersten umschließenden try -Block (falls vorhanden) nicht enthält, kann die Kopierinitialisierung des Ausnahmeobjekts weggelassen werden, indem obj direkt in das Ausnahmeobjekt konstruiert wird.
  • In einem Handler kann die Kopierinitialisierung des Handler-Arguments weggelassen werden, indem der Handler-Parameter als Alias für das Ausnahmeobjekt behandelt wird, wenn sich die Bedeutung des Programms nicht ändert, außer durch die Ausführung von Konstruktoren und Destruktoren für das Handler-Argument.
(seit C++11)
  • In Coroutinen kann eine Kopie eines Coroutinen-Parameters weggelassen werden. In diesem Fall werden Referenzen auf diese Kopie durch Referenzen auf den entsprechenden Parameter ersetzt, wenn sich die Bedeutung des Programms nicht ändert, außer durch die Ausführung eines Konstruktors und Destruktors für das Parameterkopie-Objekt.
(seit C++20)

Wenn Copy Elision auftritt, behandelt die Implementierung die Quelle und das Ziel der ausgelassenen Initialisierung einfach als zwei verschiedene Arten, sich auf dasselbe Objekt zu beziehen.

Die Zerstörung erfolgt zum späteren der beiden Zeitpunkte, zu denen die beiden Objekte ohne die Optimierung zerstört worden wären.

(until C++11)

Wenn der erste Parameter des ausgewählten Konstruktors ein Rvalue-Referenz auf den Objekttyp ist, erfolgt die Zerstörung dieses Objekts, wenn das Ziel zerstört worden wäre. Andernfalls erfolgt die Zerstörung zum späteren der beiden Zeitpunkte, zu denen die beiden Objekte ohne die Optimierung zerstört worden wären.

(since C++11)


Prvalue-Semantik ("guaranteed copy elision")

Seit C++17 wird ein Prvalue erst dann materialisiert, wenn er benötigt wird, und dann direkt in den Speicher seines endgültigen Ziels konstruiert. Dies bedeutet manchmal, dass selbst wenn die Sprachsyntax visuell einen Kopier-/Verschiebevorgang nahelegt (z.B. Copy Initialization ), kein Kopier-/Verschiebevorgang durchgeführt wird – was bedeutet, dass der Typ überhaupt keinen zugänglichen Kopier-/Verschiebekonstruktor benötigt. Beispiele hierfür sind:

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
{
Der Destruktor des zurückgegebenen Typs muss an der Stelle der return-Anweisung zugänglich und nicht gelöscht sein, auch wenn kein T-Objekt zerstört wird.
  • Bei der Initialisierung eines Objekts, wenn der Initialisierungsausdruck ein Prvalue desselben Klassentyps (unter Ignorierung der CV-Qualifikation ) wie der Variablentyp ist:
T x = T(T(f())); // x is initialized by the result of f() directly; no move
Dies kann nur angewendet werden, wenn das zu initialisierende Objekt bekanntlich kein potenziell überlappendes Subobjekt ist:
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
};

Hinweis: Diese Regel spezifiziert keine Optimierung, und der Standard beschreibt sie nicht formal als "Copy Elision" (weil nichts ausgelassen wird). Stattdessen ist die C++17-Kernsprachspezifikation von Prvalues und Temporaries grundlegend anders als die früherer C++-Revisionen: Es gibt kein temporäres Objekt mehr, von dem kopiert/verschoben werden könnte. Eine andere Beschreibung der C++17-Mechanik ist "Unmaterialized Value Passing" oder "Deferred Temporary Materialization": Prvalues werden zurückgegeben und verwendet, ohne jemals ein temporäres Objekt zu materialisieren.

(seit C++17)

Hinweise

Copy elision ist die einzige erlaubte Form der Optimierung (bis C++14) eine der zwei erlaubten Optimierungsformen, zusammen mit allocation elision and extension , (seit C++14) die beobachtbare Nebeneffekte ändern kann. Da manche Compiler Copy elision nicht in jeder erlaubten Situation durchführen (z.B. im Debug-Modus), sind Programme, die sich auf die Nebeneffekte von Copy-/Move-Konstruktoren und Destruktoren verlassen, nicht portabel.

In einer return -Anweisung oder einem throw -Ausdruck, wenn der Compiler keine Copy Elision durchführen kann, aber die Bedingungen für Copy Elision erfüllt sind oder erfüllt wären, außer dass die Quelle ein Funktionsparameter ist, wird der Compiler versuchen, den Move-Konstruktor zu verwenden, selbst wenn der Quelloperand durch einen Lvalue bezeichnet wird (bis C++23) wird der Quelloperand als Rvalue behandelt (seit C++23) ; siehe return -Anweisung für Details.

In konstanten Ausdrücken und bei konstanter Initialisierung wird Copy Elision niemals durchgeführt.

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
(seit C++11)
Feature-Test-Makro Wert Std Feature
__cpp_guaranteed_copy_elision 201606L (C++17) Garantierte Kopiereliminierung durch vereinfachte Wertkategorien

Beispiel

#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(); // (bis C++17) Copy Elision initialisiert v von einem temporären Objekt;
                       //               der Move-Konstruktor könnte aufgerufen werden
                       // (seit C++17) "Garantierte Copy Elision"
    return v; // Copy Elision ("NRVO") von v zum Ergebnisobjekt;
              // der Move-Konstruktor könnte aufgerufen werden
}
void g(Noisy arg)
{
    std::cout << "&arg = " << &arg << '\n';
}
int main()
{
    Noisy v = f(); // (bis C++17) Copy Elision initialisiert v vom Ergebnis von f()
                   // (seit C++17) "Garantierte Copy Elision"
    std::cout << "&v = " << &v << '\n';
    g(f()); // (bis C++17) Copy Elision initialisiert arg vom Ergebnis von f()
            // (seit C++17) "Garantierte Copy Elision"
}

Mögliche Ausgabe:

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

Fehlerberichte

Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR Angewendet auf Verhalten wie veröffentlicht Korrektes Verhalten
CWG 1967 C++11 wenn Copy Elision mit einem Move-Konstruktor durchgeführt wird, wurde
die Lebensdauer des verschobenen Objekts noch berücksichtigt
nicht berücksichtigt
CWG 2426 C++17 Destruktor war nicht erforderlich bei Rückgabe eines Prvalue Destruktor wird potenziell aufgerufen
CWG 2930 C++98 nur Copy(/Move)-Operationen konnten elidiert werden, aber ein
Nicht-Copy(/Move)-Konstruktor konnte durch Copy-Initialisierung ausgewählt werden
elidiert jede Objektkonstruktion
von verwandten Copy-Initialisierungen

Siehe auch