Ich versuche, rWert-Referenzen zu verstehen und die Semantik von C ++ 11 zu verschieben.
Was ist der Unterschied zwischen diesen Beispielen und welches von ihnen wird keine Vektorkopie machen?
Erstes Beispiel
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return tmp;
}
std::vector<int> &&rval_ref = return_vector();
Zweites Beispiel
std::vector<int>&& return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
Drittes Beispiel
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return std::move(tmp);
}
std::vector<int> &&rval_ref = return_vector();
c++
c++11
move-semantics
rvalue-reference
c++-faq
Tarantel
quelle
quelle
std::move()
eine dauerhafte "Kopie" erstellt wurde.std::move(expression)
erstellt nichts, sondern wandelt den Ausdruck einfach in einen x-Wert um. Während der Auswertung werden keine Objekte kopiert oder verschobenstd::move(expression)
.Antworten:
Erstes Beispiel
Das erste Beispiel gibt eine temporäre Datei zurück, die von abgefangen wird
rval_ref
. Dieses temporäre Leben wird über dierval_ref
Definition hinaus verlängert, und Sie können es so verwenden, als hätten Sie es nach Wert erfasst. Dies ist dem folgenden sehr ähnlich:außer dass Sie in meinem Umschreiben offensichtlich nicht
rval_ref
in einer nicht konstanten Weise verwenden können.Zweites Beispiel
Im zweiten Beispiel haben Sie einen Laufzeitfehler erstellt.
rval_ref
enthält nun einen Verweis auf dastmp
innerhalb der Funktion zerstörte. Mit etwas Glück würde dieser Code sofort abstürzen.Drittes Beispiel
Ihr drittes Beispiel entspricht in etwa Ihrem ersten. Die
std::move
auftmp
ist unnötig und kann tatsächlich eine Leistung Pessimierung sein , wie es Rückgabewert Optimierung hemmen.Der beste Weg, um zu codieren, was Sie tun, ist:
Beste Übung
Dh genau wie in C ++ 03.
tmp
wird in der return-Anweisung implizit als r-Wert behandelt. Es wird entweder über die Rückgabewertoptimierung (keine Kopie, keine Verschiebung) zurückgegeben, oder wenn der Compiler entscheidet, dass RVO nicht ausgeführt werden kann, wird dies getan verwendet er den Verschiebungskonstruktor des Vektors, um die Rückgabe . Nur wenn RVO nicht ausgeführt wird und der zurückgegebene Typ keinen Verschiebungskonstruktor hat, wird der Kopierkonstruktor für die Rückgabe verwendet.quelle
return my_local;
. Mehrere Rückgabeanweisungen sind in Ordnung und hemmen RVO nicht.move
erstellt keine temporäre. Es wandelt einen l-Wert in einen x-Wert um, erstellt keine Kopien, erstellt nichts und zerstört nichts. Dieses Beispiel ist genau die gleiche Situation, als ob Sie per lvalue-reference zurückgegeben und diemove
aus der Rückgabezeile entfernt hätten: In beiden Fällen haben Sie eine baumelnde Referenz auf eine lokale Variable innerhalb der Funktion, die zerstört wurde.Keiner von ihnen wird kopiert, aber der zweite bezieht sich auf einen zerstörten Vektor. Benannte rWertreferenzen existieren im regulären Code fast nie. Sie schreiben es so, wie Sie eine Kopie in C ++ 03 geschrieben hätten.
Außer jetzt wird der Vektor verschoben. Der Benutzer einer Klasse befasst sich in den allermeisten Fällen nicht mit ihren Wertreferenzen.
quelle
tmp
wird nicht bewegt inrval_ref
, sondern direkt in geschriebenrval_ref
mit RVO (dh kopieren elision). Es gibt einen Unterschied zwischenstd::move
und Kopierentscheidung. A enthältstd::move
möglicherweise noch einige zu kopierende Daten. Im Fall eines Vektors wird tatsächlich ein neuer Vektor im Kopierkonstruktor erstellt und Daten werden zugewiesen, aber der Großteil des Datenarrays wird nur durch Kopieren des Zeigers (im Wesentlichen) kopiert. Die Kopierentfernung vermeidet 100% aller Kopien.rval_ref
Variablen mit dem Verschiebungskonstruktor von erstelltstd::vector
. Es ist kein Kopierkonstruktor mit / ohne beteiligtstd::move
.tmp
wird in diesem Fall in der Anweisung als rWert behandeltreturn
.Die einfache Antwort ist, dass Sie Code für r-Wert-Referenzen schreiben sollten, wie Sie regulären Referenzcode verwenden würden, und dass Sie sie 99% der Zeit mental gleich behandeln sollten. Dies schließt alle alten Regeln zum Zurückgeben von Referenzen ein (dh niemals einen Verweis auf eine lokale Variable zurückgeben).
Sofern Sie keine Vorlagencontainerklasse schreiben, die std :: forward nutzen und eine generische Funktion schreiben muss, die entweder lvalue- oder rvalue-Referenzen verwendet, ist dies mehr oder weniger richtig.
Einer der großen Vorteile des Verschiebungskonstruktors und der Verschiebungszuweisung besteht darin, dass der Compiler sie verwenden kann, wenn Sie sie definieren, wenn die RVO (Rückgabewertoptimierung) und NRVO (benannte Rückgabewertoptimierung) nicht aufgerufen werden. Dies ist ziemlich umfangreich, um teure Objekte wie Container und Strings wertmäßig effizient aus Methoden zurückzugeben.
Wenn rvalue-Referenzen interessant werden, können Sie sie auch als Argumente für normale Funktionen verwenden. Auf diese Weise können Sie Container schreiben, die sowohl für die const-Referenz (const foo & other) als auch für die rvalue-Referenz (foo && other) überladen sind. Selbst wenn das Argument zu unhandlich ist, um mit einem bloßen Konstruktoraufruf übergeben zu werden, kann es dennoch getan werden:
Die STL-Container wurden aktualisiert, um Verschiebungsüberladungen für fast alles (Hash-Schlüssel und -Werte, Vektoreinfügung usw.) zu haben, und dort werden Sie sie am häufigsten sehen.
Sie können sie auch für normale Funktionen verwenden. Wenn Sie nur ein rvalue-Referenzargument angeben, können Sie den Aufrufer zwingen, das Objekt zu erstellen und die Funktion die Verschiebung ausführen zu lassen. Dies ist eher ein Beispiel als eine wirklich gute Verwendung, aber in meiner Rendering-Bibliothek habe ich allen geladenen Ressourcen eine Zeichenfolge zugewiesen, damit Sie leichter sehen können, was jedes Objekt im Debugger darstellt. Die Schnittstelle ist ungefähr so:
Es ist eine Form einer "undichten Abstraktion", aber ich kann die Tatsache ausnutzen, dass ich die Zeichenfolge bereits die meiste Zeit erstellen musste, und vermeiden, sie erneut zu kopieren. Dies ist nicht gerade ein Hochleistungscode, aber ein gutes Beispiel für die Möglichkeiten, wenn die Leute den Dreh raus haben. Dieser Code erfordert, dass die Variable entweder temporär für den Aufruf ist oder std :: move aufgerufen wird:
oder
oder
aber das wird nicht kompiliert!
quelle
Keine Antwort an sich , sondern eine Richtlinie. Meistens macht es wenig Sinn, lokale
T&&
Variablen zu deklarieren (wie Sie es getan habenstd::vector<int>&& rval_ref
). Sie müssen sie weiterhinstd::move()
infoo(T&&)
Typmethoden verwenden. Es gibt auch das Problem, das bereits erwähnt wurde, wenn Sie versuchen, solche zurückzugebenrval_ref
Funktion zurückzugeben, den Standardverweis auf das zerstörte temporäre Fiasko erhalten.Die meiste Zeit würde ich mit folgendem Muster gehen:
Sie haben keine Verweise auf zurückgegebene temporäre Objekte und vermeiden so (unerfahrene) Programmiererfehler, die ein verschobenes Objekt verwenden möchten.
Offensichtlich gibt es (wenn auch eher seltene) Fälle, in denen eine Funktion wirklich a zurückgibt,
T&&
was eine Referenz auf ein nicht temporäres Objekt ist, das Sie in Ihr Objekt verschieben können.In Bezug auf RVO: Diese Mechanismen funktionieren im Allgemeinen und der Compiler kann das Kopieren gut vermeiden, aber in Fällen, in denen der Rückweg nicht offensichtlich ist (Ausnahmen,
if
Bedingungen, die das benannte Objekt bestimmen, das Sie zurückgeben werden, und wahrscheinlich andere koppeln), sind Rrefs Ihre Retter (auch wenn möglicherweise mehr teuer).quelle
Keiner von diesen wird extra kopieren. Selbst wenn RVO nicht verwendet wird, besagt der neue Standard, dass die Verschiebungskonstruktion bei Rückgaben meiner Meinung nach dem Kopieren vorgezogen wird.
Ich glaube jedoch, dass Ihr zweites Beispiel undefiniertes Verhalten verursacht, da Sie einen Verweis auf eine lokale Variable zurückgeben.
quelle
Wie bereits in den Kommentaren zur ersten Antwort erwähnt, kann das
return std::move(...);
Konstrukt in anderen Fällen als der Rückgabe lokaler Variablen einen Unterschied machen. Hier ist ein ausführbares Beispiel, das dokumentiert, was passiert, wenn Sie ein Mitgliedsobjekt mit und ohne zurückgebenstd::move()
:Vermutlich ist dies
return std::move(some_member);
nur dann sinnvoll, wenn Sie das bestimmte Klassenmitglied tatsächlich verschieben möchten, z. B. in einem Fall, in demclass C
kurzlebige Adapterobjekte mit dem alleinigen Zweck dargestellt werden, Instanzen von zu erstellenstruct A
.Beachten Sie, wie
struct A
immer kopiert wirdclass B
, auch wenn dasclass B
Objekt ein R-Wert ist. Dies liegt daran, dass der Compiler nicht erkennen kann, dassclass B
die Instanz vonstruct A
nicht mehr verwendet wird. Inclass C
hat der Compiler diese Informationen vonstd::move()
, weshalbstruct A
bekommt bewegt , es sei denn , die Instanz vonclass C
konstant ist.quelle