Copy elision
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.
|
(bis C++17) |
|
(seit C++11) |
|
(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 {
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 }; 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 |