copy elision
optimiert die Copy- und Move- (seit C++11)Konstruktoren weg und führt so zu einer kopierfreien Pass-by-Value-Semantik.
Inhaltsverzeichnis |
[Bearbeiten] Erklärung
Unter folgenden Umständen müssen Kompiler die Copy- und Move-Konstruktoren der Klassenobjekte wegoptimieren, auch wenn der Copy-Konstruktor, der Move-Konstruktor oder der Destruktor sichtbare Nebenwirkungen besitzt.
T x = T(T(T())); // nur einmaliger Aufruf des Default-Konstruktors von T, um x zu initialiseren
T f() { return T{}; } T x = f(); // nur einmaliger Aufruf des Default-Konstruktors von T, um x zu initialiseren T* p = new T(f()); // nur einmaliger Aufruf des Default-Konstruktors von T, um *p zu initialiseren |
(seit C++17) |
Unter folgenden Umständen ist es Kompilern gestattet, Copy- und Move- (seit C++11)Konstruktoren von Klassenobjekten wegzuoptimieren, auch wenn der Copy-Konstruktor, der Move-Konstruktor (seit C++11) oder Destruktor sichtbare Nebenwirkungen hat.
- Falls eine Funktion einen Klassentyp als Wert zurückgibt und die Return-Anweisung aus dem Name eines nichtflüchtigen Objektes mit automatischer Speicherdauer besteht, das weder ein Aufrufparameter der Funktion noch der Parameter einer Catch-Klausel ist, und unter Nichtberücksichtigung der cv-Qualifikation vom selben Typ wie der Rückgabetyp der Funktion ist, so wird die Kopie bzw. das Verschieben (seit C++11) unterlassen. Die lokale Variable wird direkt an der Speicherstelle erzeugt, zu der der Rückgabewert andernfalls kopiert bzw. verschoben (seit C++11) werden würde. Diese Variante der copy elision wird als NRVO (Named Return Value Optimization) bezeichnet.
|
(bis C++17) |
|
(seit C++11) |
|
(seit C++11) |
Falls das Kopieren bzw. Verschieben (seit C++11) unterlassen wird, werden die Quelle und das Ziel der nicht durchgeführten Kopieroperation (bis C + +11) Kopier- bzw. Verschiebeoperation (seit C++11) durch den Kompiler einfach als zwei Zugriffsmöglichketen auf dasselbe Objekt behandelt. Die Zerstörung des Objektes durch einen einzigen Destruktoraufruf erfolgt dann zum Zeitpunkt, wenn das Letzte der zwei Objekte ohne diese Optimierung zerstört worden wäre. (except that, if the parameter of the selected constructor is an rvalue reference to object type, the destruction occurs when the target would have been destroyed) (seit C++17).
Das Auslassungen der Kopieroperation (bis C + +11) Kopier- bzw. Verschiebeoperation (seit C++11) darf beliebig verkettet werden.
struct A { void *p; constexpr A(): p(this) {} }; constexpr A g() { A a; return a; } constexpr A a; // a.p points to a constexpr A b = g(); // b.p points to b (NRVO guaranteed) void g() { A c = g(); // c.p may point to c or to an ephemeral temporary } |
(seit C++14) |
[Bearbeiten] Anmerkungen
Copy elision ist die einzige erlaubte Form der Optimierung (bis C++14)eine der beiden erlaubten Formen der Optimierung (neben allocation elision and extension) (seit C++14), die beobachtbare Nebenwirkungen ändern können. Da nicht alle Kompiler copy elison in jeder erlaubten Situation benutzen (z.B. ohne Optimierung), sind Programme, die auf den Nebenwirkungen von Copy- bzw. Move- (seit C++11)Konstruktoren und Destruktoren angewiesen sind, nicht ohne weiteres portierbar.
Falls copy elision benutzt wird, obwohl sie nicht garantiert ist, (seit C++17) und der Copy- bzw. Move- (seit C++11)Konstruktor nicht aufgerufen wird, muß er jedoch vorhanden und aufrufbar sein als ob keine Optimierung stattfinden würde. Andernfalls ist das Programm nicht regelkonform.
Falls der Kompiler innerhalb einer Rückgabeanweisung oder eines Throw-Ausdrucks keine copy elision durchführen kann, obwohl die Bedingungen für diese erfüllt sind oder sein würden, so versucht der Kompiler mit der Ausnahme, daß die Quelle ein Funktionsparameter ist, den Move-Konstruktor zu benutzen, auch wenn das Objekt durch einen lvalue beschrieben wird (siehe hierzu return statement). |
(seit C++11) |
[Bearbeiten] Beispiele
#include <iostream> #include <vector> struct Noisy { Noisy() { std::cout << "constructed\n"; } Noisy(const Noisy&) { std::cout << "copy-constructed\n"; } Noisy(Noisy&&) { std::cout << "move-constructed\n"; } ~Noisy() { std::cout << "destructed\n"; } }; std::vector<Noisy> f() { std::vector<Noisy> v = std::vector<Noisy>(3); // copy elision: Initialisierung von v // aus temporärem Objekt // (garantiert seit C++17) return v; // NRVO: von v ins unbenamte, temporäe Rückgabeobjekt (nicht garantiert) } // oder Benutzung des Move-Konstruktor, falls die Optimierung nicht verwendet wird. void g(std::vector<Noisy> arg) { std::cout << "arg.size() = " << arg.size() << '\n'; } int main() { std::vector<Noisy> v = f(); // copy elision: Initialisierung von v // aus unbenamten, temporären Rückgabeobjekt von f() // (garantiert seit C++17) g(f()); // copy elision: Initialiserung des Parameters von g() // aus unbenamten, temporären Rückgabeobjekt von f() // (garantiert seit C++17) }
Possible output:
constructed constructed constructed constructed constructed constructed arg.size() = 3 destructed destructed destructed destructed destructed destructed
[Bearbeiten] Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte werden nachträglich auf schon veröffentlichte C++-Standards angewandt.
DR | angewandt auf | Verhalten im Standard | korrigiertes Verhalten |
---|---|---|---|
CWG 2022 | C++14 | copy elision war nur optional in konstanten Ausdrücken | Nun ist copy elision zwingend erforderlich. |