Lets spielen mit NRVO, RVO und Kopie Elision!Hier
ist ein Typ:
#include <iostream>
struct Verbose {
Verbose(Verbose const&){ std::cout << "copy ctor\n"; }
Verbose(Verbose &&){ std::cout << "move ctor\n"; }
Verbose& operator=(Verbose const&){ std::cout << "copy asgn\n"; }
Verbose& operator=(Verbose &&){ std::cout << "move asgn\n"; }
};
, die ziemlich ausführlich ist. Hier
ist eine Funktion:
Verbose simple() { return {}; }
, die ziemlich einfach ist, und verwendet die direkte Konstruktion seines Rückgabewert. Wenn Verbose
ein Kopie- oder Move-Konstruktor fehlte, würde die obige Funktion funktionieren!
Hier ist eine Funktion, die RVO verwendet:
Verbose simple_RVO() { return Verbose(); }
hier die unbenannte Verbose()
temporäre Objekt erzählt wird sich auf den Rückgabewert zu kopieren. RVO bedeutet, dass der Compiler diese Kopie überspringen und direkt Verbose()
in den Rückgabewert konstruieren kann, genau dann, wenn es einen Copy- oder Move-Konstruktor gibt. Der Copy- oder Move-Konstruktor wird nicht aufgerufen, sondern eher weggelassen.
Hier ist eine Funktion, die NRVO verwendet:
Verbose simple_NRVO() {
Verbose retval;
return retval;
}
Für NRVO jeder Pfad zu kommen, muss genau das gleiche Objekt zurück, und Sie können darüber nicht hinterhältig sein (wenn Sie den Rückgabewert zu gieße eine Referenz, dann die Referenz zurückgeben, die NRVO blockiert). In diesem Fall konstruiert der Compiler das benannte Objekt retval
direkt in die Rückgabewertposition. Ähnlich wie bei RVO muss ein Copy- oder Move-Konstruktor existieren, wird aber nicht aufgerufen.
Hier ist eine Funktion, die NRVO zu verwenden, schlägt fehl:
Verbose simple_no_NRVO(bool b) {
Verbose retval1;
Verbose retval2;
if (b)
return retval1;
else
return retval2;
}
, da es nicht beide von ihnen in der Rückgabewert Lage sind zwei es möglich, benannte Objekte könnten zurückkehren konstruieren können, so dass es tun muss eine tatsächliche Kopie. In C++ 11 wird das zurückgegebene Objekt implizit move
d statt kopiert, da es sich um eine lokale Variable handelt, die von einer Funktion in einer einfachen return-Anweisung zurückgegeben wird. So ist es zumindest.
Schließlich gibt es Kopie elision am anderen Ende:
Verbose v = simple(); // or simple_RVO, or simple_NRVO, or...
Wenn Sie eine Funktion aufrufen, Sie bieten es mit seinen Argumenten, und Sie es informieren, wo sie ihren Rückgabewert setzen sollte. Der Aufrufer ist dafür verantwortlich, den Rückgabewert zu bereinigen und den Speicher (auf dem Stapel) dafür zuzuweisen.
Diese Kommunikation erfolgt in gewisser Weise über die Aufrufkonvention, oft implizit (dh über den Stack-Pointer).
Unter vielen Aufrufkonventionen kann die Position, an der der Rückgabewert gespeichert werden kann, als lokale Variable verwendet werden.
Im Allgemeinen, wenn Sie eine Variable des Formulars haben:
Verbose v = Verbose();
die implizite Kopie elided werden kann - Verbose()
direkt in v
konstruiert, anstatt eine temporäre dann auf v
kopiert erstellt werden.Auf die gleiche Weise kann der Rückgabewert von simple
(oder simple_NRVO
oder was auch immer) entfernt werden, wenn das Laufzeitmodell des Compilers dies unterstützt (und dies normalerweise tut).
Grundsätzlich kann der aufrufende Standort simple_*
anweisen, den Rückgabewert an einer bestimmten Stelle zu setzen und diesen Punkt einfach als lokale Variable v
zu behandeln.
Beachten Sie, dass NRVO und RVO und implizite Verschiebung sind alle innerhalb der Funktion getan, und der Anrufer muss nichts davon wissen.
In ähnlicher Weise ist die eliding an der aufrufenden Website außerhalb der Funktion getan, und wenn die Aufrufkonvention es unterstützt, benötigen Sie keine Unterstützung aus dem Körper der Funktion.
Dies muss nicht in jeder Aufrufkonvention und in jedem Laufzeitmodell zutreffen, sodass der C++ - Standard diese Optimierungen optional macht.
Jeder Compiler kann wählen, was er will, und wenn es ein Problem für sie in dem von Ihnen beschriebenen Fall ist, werden sie wahrscheinlich immer RVO verwenden. Wenn Sie daran interessiert sind, wie verschiedene Compiler es unter die Haube tun, empfehle ich Ihnen, den generierten Assembler-Code zu lesen. Verwenden Sie den GCC-Explorer für den einfachen Zugriff auf die von clang/gcc generierte Baugruppe. – PlasmaHH
@PlasmaHH Aber RVO ist nicht immer möglich. –
Das bedeutet nicht, dass die Aufrufkonvention sich ändert, wenn sie verwendet wird oder nicht. Sehen Sie sich einen Assembler an, wie er es macht. – PlasmaHH