Sie erklärt eine rWert-Referenz (Normenvorschlag doc).
Hier ist eine Einführung in rvalue Referenzen .
Hier ist eine fantastische eingehenden Blick auf rvalue Referenzen von einem von Microsofts Standard-Bibliothek Entwickler .
VORSICHT! der verlinkte Artikel auf MSDN ("Rvalue References: C++0x Features in VC10, Part 2") ist eine sehr klare Einführung in Rvalue-Referenzen, macht aber Aussagen über Rvalue-Referenzen, die einst im Entwurf des C++11-Standards wahr waren, aber für den endgültigen Standard nicht mehr gelten! Insbesondere heißt es an verschiedenen Stellen, dass rvalue-Referenzen an lvalues binden können, was einmal wahr war, aber geändert wurde.(z.B. int x; int &&rrx = x; kompiliert nicht mehr in GCC) - drewbarbs Jul 13 '14 at 16:12
Der größte Unterschied zwischen einer C++03-Referenz (in C++11 jetzt lvalue-Referenz genannt) besteht darin, dass sie wie ein temporärer Wert an einen rvalue binden kann, ohne const sein zu müssen. Somit ist diese Syntax jetzt legal:
T&& r = T();
rvalue-Referenzen bieten in erster Linie folgende Möglichkeiten:
Semantik verschieben . Ein move-Konstruktor und ein move-Zuweisungsoperator können nun definiert werden, die eine rvalue-Referenz anstelle der üblichen const-lvalue-Referenz annehmen. Eine Verschiebung funktioniert wie eine Kopie, mit dem Unterschied, dass die Quelle nicht unverändert bleibt, sondern normalerweise so verändert wird, dass sie die verschobenen Ressourcen nicht mehr besitzt. Dies eignet sich hervorragend, um überflüssige Kopien zu eliminieren, insbesondere in Standardbibliotheksimplementierungen.
Ein Kopierkonstruktor könnte zum Beispiel wie folgt aussehen:
foo(foo const& other)
{
this->length = other.length;
this->ptr = new int[other.length];
copy(other.ptr, other.ptr + other.length, this->ptr);
}
Würde diesem Konstruktor ein temporäres Element übergeben, wäre die Kopie unnötig, da wir wissen, dass das temporäre Element einfach zerstört wird; warum also nicht die Ressourcen nutzen, die das temporäre Element bereits zugewiesen hat? In C++03 gibt es keine Möglichkeit, die Kopie zu verhindern, da wir nicht feststellen können, ob uns ein temporäres Element übergeben wurde. In C++11 können wir einen move-Konstruktor überladen:
foo(foo&& other)
{
this->length = other.length;
this->ptr = other.ptr;
other.length = 0;
other.ptr = nullptr;
}
Der große Unterschied ist, dass der Konstruktor move sein Argument tatsächlich ändert. Dadurch wird das temporäre Objekt effektiv in das zu konstruierende Objekt "verschoben", wodurch die unnötige Kopie entfällt.
Der move-Konstruktor wird für temporäre Werte und für nicht-konstante l-Wert-Referenzen verwendet, die explizit in r-Wert-Referenzen umgewandelt werden, indem die std::move
Funktion (sie führt nur die Konvertierung durch). Der folgende Code ruft beide den Konstruktor move für f1
y f2
:
foo f1((foo())); // Move a temporary into f1; temporary becomes "empty"
foo f2 = std::move(f1); // Move f1 into f2; f1 is now "empty"
Perfekte Weiterleitung . rvalue-Referenzen ermöglichen es uns, Argumente für Vorlagenfunktionen korrekt weiterzuleiten. Nehmen Sie zum Beispiel diese Fabrikfunktion:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1& a1)
{
return std::unique_ptr<T>(new T(a1));
}
Wenn wir die factory<foo>(5)
wird das Argument wie folgt hergeleitet int&
die nicht an eine 5 gebunden wird, auch wenn foo
Konstruktor nimmt eine int
. Nun, wir könnten stattdessen Folgendes verwenden A1 const&
aber was wäre, wenn foo
das Konstruktorargument als Nicht-Konst-Referenz übernimmt? Um eine wirklich generische Fabrikfunktion zu erstellen, müssten wir factory überladen auf A1&
und weiter A1 const&
. Das mag in Ordnung sein, wenn die Fabrik nur einen Parametertyp annimmt, aber jeder weitere Parametertyp würde die erforderliche Überlastmenge mit 2 multiplizieren. Das ist sehr schnell unwartbar.
rvalue-Referenzen beheben dieses Problem, indem sie es der Standardbibliothek ermöglichen, eine std::forward
Funktion, die lvalue/rvalue-Referenzen korrekt weiterleiten kann. Für weitere Informationen darüber, wie std::forward
Werke, siehe diese ausgezeichnete Antwort .
Dies ermöglicht es uns, die Fabrikfunktion wie folgt zu definieren:
template <typename T, typename A1>
std::unique_ptr<T> factory(A1&& a1)
{
return std::unique_ptr<T>(new T(std::forward<A1>(a1)));
}
Jetzt bleibt die rvalue/lvalue-ness des Arguments erhalten, wenn es an T
Konstrukteur. Das bedeutet, dass wenn factory mit einem rWert aufgerufen wird, T
Konstruktor mit einem r-Wert aufgerufen wird. Wenn factory mit einem l-Wert aufgerufen wird, T
Konstruktor mit einem l-Wert aufgerufen wird. Die verbesserte Fabrikfunktion funktioniert aufgrund einer besonderen Regel:
Wenn der Funktionsparameter vom Typ der Form T&&
où T
ist eine Vorlage Parameter, und das Funktionsargument ist ein l-Wert vom Typ A
der Typ A&
ist für die Ableitung von Vorlagenargumenten verwendet.
Wir können die Fabrik also wie folgt verwenden:
auto p1 = factory<foo>(foo()); // calls foo(foo&&)
auto p2 = factory<foo>(*p1); // calls foo(foo const&)
Wichtige rvalue-Referenzeigenschaften :
- Für die Überlastauflösung, lWerte bevorzugen die Bindung an lWert-Referenzen und rWerte die Bindung an rWert-Referenzen . Daher bevorzugen Provisorien den Aufruf eines Verschiebekonstruktors / Verschiebezuweisungsoperators gegenüber einem Kopierkonstruktor / Zuweisungsoperator.
- rvalue-Referenzen werden implizit an rvalues und an temporäre Werte gebunden, die das Ergebnis einer impliziten Konvertierung sind . d.h.
float f = 0f; int&& i = f;
ist wohlgeformt, da float implizit in int konvertierbar ist; der Verweis würde auf ein temporäres Element erfolgen, das das Ergebnis der Konvertierung ist.
- Benannte rWert-Referenzen sind lWerte. Unbenannte rWert-Referenzen sind rWerte. Dies ist wichtig, um zu verstehen, warum die
std::move
ist ein Anruf erforderlich: foo&& r = foo(); foo f = std::move(r);