Sehen Sie sich zunächst folgenden Code an, der aus 2 Übersetzungseinheiten besteht.
%Vor%Bitte stellen Sie sicher, dass foo.cpp und main.cpp unterschiedliche Übersetzungseinheiten sind. Nach meinem Verständnis können wir sagen, dass es keine Implementierungsdetails von getFoo () in der Übersetzungseinheit main.o (main.cpp) gibt.
Wenn wir jedoch das obige kompilieren und ausführen, konnte ich die Zeichenfolge "Copy Ctor" nicht sehen, die anzeigt, dass RVO hier arbeitet.
Es würde mich sehr freuen, wenn jemand von euch mir freundlicherweise mitteilen würde, wie dies erreicht werden kann, selbst wenn die Implementierungsdetails von 'getFoo ()' nicht der Übersetzungseinheit main.o ausgesetzt sind?
Ich habe das obige Experiment unter Verwendung von GCC (g ++) 4.4.6 durchgeführt.
Der Compiler muss einfach konsistent arbeiten.
Mit anderen Worten, der Compiler muss nur auf einen return -Typ schauen und basierend auf diesem Typ entscheiden, wie eine Funktion, die ein Objekt dieses Typs zurückgibt, den Wert zurückgibt.
Zumindest in einem typischen Fall ist diese Entscheidung ziemlich trivial. Es reserviert ein Register (oder möglicherweise zwei), das für Rückgabewerte verwendet wird (z. B. auf einem Intel / AMD x86 / x64, der normalerweise EAX oder RAX ist). Jeder Typ, der klein genug ist, um darin zu passen, wird dorthin zurückgebracht. Für jeden Typ, der zu groß ist, um dorthin zu passen, erhält die Funktion einen versteckten Zeiger / Referenzparameter, der angibt, wo das Rückgabeergebnis abgelegt werden soll. Beachten Sie, dass dies gilt, ohne dass RVO / NRVO überhaupt beteiligt ist - in der Tat gilt es gleichermaßen für C-Code, der ein struct
zurückgibt, wie auch für C ++, das ein class
-Objekt zurückgibt. Obwohl das Zurückgeben von struct
in C wahrscheinlich nicht ganz so häufig ist wie in C ++, ist es immer noch erlaubt, und der Compiler muss in der Lage sein, Code zu kompilieren, der das tut.
Es gibt wirklich zwei separate (mögliche) Kopien, die eliminiert werden können. Einer ist, dass der Compiler Platz auf dem Stapel für einen lokalen Betrieb reservieren kann, was der Rückgabewert sein wird, und dann von dort dorthin kopieren, wo der Zeiger während der Rückkehr verweist.
Die zweite ist eine mögliche Kopie von dieser Absenderadresse an einen anderen Ort, wo der Wert wirklich enden muss.
Der erste wird innerhalb der Funktion selbst eliminiert, hat aber keine Auswirkungen auf seine externe Schnittstelle. Sie legt die Daten schließlich an die Stelle, an die der versteckte Zeiger sie angibt - die einzige Frage ist, ob sie zuerst eine lokale Kopie erstellt oder immer direkt mit dem Rückkehrpunkt arbeitet. Offensichtlich funktioniert es bei [N] RVO immer direkt.
Die zweite mögliche Kopie ist von diesem (potentiellen) temporären Ort, wo immer der Wert wirklich benötigt wird. Dies wird eliminiert, indem die Aufrufsequenz und nicht die Funktion selbst optimiert wird, dh der Funktion ein Zeiger auf das endgültige Ziel für diesen Rückgabewert und nicht auf eine temporäre Stelle gegeben wird, von der der Compiler dann den Wert in sein Ziel kopiert .
main
benötigt nicht die Implementierungsdetails von getFoo
für das Auftreten von RVO. Es erwartet einfach, dass der Rückgabewert in einem Register ist, nachdem getFoo
beendet wurde.
getFoo
hat zwei Optionen dafür: Erstelle ein Objekt in seinem Bereich und kopiere (oder verschiebe es) in das Rückgabe-Register, oder erstelle das Objekt direkt in diesem Register . Was passiert ist.
Es ist nicht wichtig zu sagen, woanders zu suchen, noch muss es. Es verwendet nur das Rückgabe-Register direkt.
(N) RVO hat nichts mit den Übersetzungseinheiten zu tun. Der Begriff wird häufig verwendet, um auf zwei verschiedene Kopienelisionen zu verweisen, die innerhalb der Funktion (von einer lokalen Variablen auf den zurückgegebenen Wert) und vom Aufrufer (vom zurückgegebenen Wert auf eine lokale Variable) angewendet werden können und diskutiert werden sollten getrennt.
Ordnungsgemäße RVO
Dies wird streng innerhalb einer Funktion ausgeführt, beachte:
%Vor% Konzeptionell gibt es zwei Objekte, local
und das zurückgegebene Objekt. Der Compiler kann die Funktion lokal analysieren und feststellen, dass die Lebensdauer beider Objekte gebunden ist: local
dient nur als Quelle für eine Kopie des zurückgegebenen Werts. Der Compiler kann dann beide Variablen in einer einzigen Variablen binden und verwenden.
Elision auf der Anruferseite kopieren
Berücksichtigen Sie auf der Anruferseite T x = foo();
. Auch hier gibt es zwei Objekte, das zurückgegebene Objekt von foo()
und x
. Und wieder kann der Compiler bestimmen, dass die Lebensdauern gebunden sind und beide Objekte an der gleichen Stelle platzieren.
Lesen Sie weiter: