Ich habe mich mit einigen der neuen Features von C++11 beschäftigt und mir ist dabei das doppelte kaufmännische Und beim Deklarieren von Variablen aufgefallen, wie z T&& var
.
Zunächst einmal, wie heißt dieses Biest? Ich wünschte, Google würde uns erlauben, nach solchen Satzzeichen zu suchen.
Was genau macht es bedeuten?
Auf den ersten Blick scheint es sich um eine Doppelreferenz zu handeln (wie die Doppelzeiger im C-Stil T** var
), aber es fällt mir schwer, an einen Anwendungsfall dafür zu denken.
Es erklärt ein rvalue-Referenz (Standardvorschlagsdokument).
Hier ist eine Einführung in rvalue Verweise.
Hier ist ein fantastischer detaillierter Blick auf rvalue-Referenzen von einer der Standardbibliotheken von Microsoft 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 C++11-Standardentwurf galten, gelten aber nicht für das letzte! Insbesondere heißt es an verschiedenen Stellen, dass rvalue-Referenzen an lvalues binden können, was einst wahr war, aber geändert wurde (z. B. int x; int &&rrx = x; nicht mehr in GCC kompiliert)
Der größte Unterschied zwischen einer C++03-Referenz (jetzt Lvalue-Referenz in C++11 genannt) besteht darin, dass sie wie ein temporärer Wert an einen Rvalue gebunden werden kann, ohne konstant sein zu müssen. Somit ist diese Syntax jetzt legal:
T&& r = T();
rvalue-Referenzen bieten hauptsächlich Folgendes:
Semantik bewegen. Es können jetzt ein Bewegungskonstruktor und ein Bewegungszuweisungsoperator definiert werden, die eine rvalue-Referenz anstelle der üblichen const-lvalue-Referenz annehmen. Eine Verschiebung funktioniert wie eine Kopie, außer dass sie nicht verpflichtet ist, die Quelle unverändert zu lassen; Tatsächlich ändert es normalerweise die Quelle so, dass es die verschobenen Ressourcen nicht mehr besitzt. Dies ist großartig, um überflüssige Kopien zu eliminieren, insbesondere in Implementierungen von Standardbibliotheken.
Ein Kopierkonstruktor könnte beispielsweise so aussehen:
foo(foo const& other)
{
this->length = other.length;
this->ptr = new int[other.length];
copy(other.ptr, other.ptr + other.length, this->ptr);
}
Wenn diesem Konstruktor ein temporäres übergeben würde, wäre die Kopie unnötig, da wir wissen, dass das temporäre nur zerstört wird; Warum nicht die Ressourcen nutzen, die das Temporär bereits zugewiesen hat? In C++03 gibt es keine Möglichkeit, das Kopieren zu verhindern, da wir nicht feststellen können, ob uns ein temporäres ü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;
}
Beachten Sie hier den großen Unterschied: Der Move-Konstruktor ändert tatsächlich sein Argument. Dies würde das Provisorium effektiv in das zu konstruierende Objekt “bewegen”, wodurch die unnötige Kopie eliminiert würde.
Der Move-Konstruktor würde für temporäre Dateien und für nicht konstante lvalue-Referenzen verwendet, die mithilfe von explizit in rvalue-Referenzen konvertiert werden std::move
Funktion (es führt nur die Konvertierung durch). Der folgende Code ruft beide den Move-Konstruktor für auf f1
und 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 ordnungsgemäß 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 anriefen factory<foo>(5)
wird das Argument abgeleitet werden int&
die nicht an ein Literal 5 gebunden wird, selbst wenn foo
Der Konstruktor von nimmt eine int
. Nun, wir könnten stattdessen verwenden A1 const&
Aber was wenn foo
Nimmt das Konstruktor-Argument als Nicht-Konstanten-Referenz? Um eine wirklich generische Factory-Funktion zu erstellen, müssten wir factory on überladen A1&
und weiter A1 const&
. Das mag in Ordnung sein, wenn Factory 1 Parametertyp verwendet, aber jeder zusätzliche Parametertyp würde das erforderliche Overload-Set mit 2 multiplizieren. Das ist sehr schnell nicht mehr zu warten.
rvalue-Referenzen beheben dieses Problem, indem sie der Standardbibliothek erlauben, a zu definieren std::forward
Funktion, die lvalue/rvalue-Referenzen ordnungsgemäß weiterleiten kann. Weitere Informationen darüber, wie std::forward
funktioniert, siehe diese ausgezeichnete Antwort.
Dies ermöglicht es uns, die Factory-Funktion 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 wird die rvalue/lvalue-ness des Arguments beibehalten, wenn es übergeben wird T
Der Konstruktor von . Das heißt, wenn Factory mit einem Rvalue aufgerufen wird, T
Der Konstruktor von wird mit einem Rvalue aufgerufen. Wenn factory mit einem lvalue aufgerufen wird, T
Der Konstruktor von wird mit einem Lvalue aufgerufen. Die verbesserte Fabrikfunktion funktioniert aufgrund einer besonderen Regel:
Wenn der Funktionsparametertyp das Format hat T&&
wo T
ist ein Vorlagenparameter, und das Funktionsargument ist ein lvalue vom Typ A
der Typ A&
wird für die Ableitung von Vorlagenargumenten verwendet.
Somit können wir die Fabrik 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 lvalues bevorzugen die Bindung an lvalue-Referenzen und rvalues bevorzugen die Bindung an rvalue-Referenzen. Daher ziehen es Temporäre vor, einen Verschiebekonstruktor/Verschiebezuweisungsoperator gegenüber einem Kopierkonstruktor/Zuweisungsoperator aufzurufen.
- Rvalue-Referenzen werden implizit an Rvalues und temporäre Dateien gebunden, die das Ergebnis einer impliziten Konvertierung sind. dh
float f = 0f; int&& i = f;
ist wohlgeformt, weil float implizit in int konvertierbar ist; der Verweis wäre auf ein temporäres Objekt, das das Ergebnis der Konvertierung ist.
- Benannte Rvalue-Referenzen sind Lvalues. Unbenannte Rvalue-Referenzen sind Rvalues. Dies ist wichtig zu verstehen, warum die
std::move
Anruf ist erforderlich in: foo&& r = foo(); foo f = std::move(r);

Hündchen
Es bezeichnet eine Rvalue-Referenz. Rvalue-Referenzen werden nur an temporäre Objekte gebunden, sofern nicht ausdrücklich anders generiert. Sie werden verwendet, um Objekte unter bestimmten Umständen viel effizienter zu machen und um eine Funktion bereitzustellen, die als perfekte Weiterleitung bekannt ist und den Vorlagencode erheblich vereinfacht.
In C++03 können Sie nicht zwischen einer Kopie eines nicht veränderlichen lvalue und einem rvalue unterscheiden.
std::string s;
std::string another(s); // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(const std::string&);
In C++0x ist dies nicht der Fall.
std::string s;
std::string another(s); // calls std::string(const std::string&);
std::string more(std::string(s)); // calls std::string(std::string&&);
Betrachten Sie die Implementierung hinter diesen Konstruktoren. Im ersten Fall muss die Zeichenfolge eine Kopie ausführen, um die Wertsemantik beizubehalten, was eine neue Heap-Zuordnung beinhaltet. Im zweiten Fall wissen wir jedoch im Voraus, dass das an unseren Konstruktor übergebene Objekt sofort zur Vernichtung fällig ist und nicht unberührt bleiben muss. Wir können in diesem Szenario effektiv nur die internen Zeiger austauschen und überhaupt kein Kopieren durchführen, was wesentlich effizienter ist. Move-Semantik kommt jeder Klasse zugute, die teures oder verbotenes Kopieren von intern referenzierten Ressourcen hat. Betrachten Sie den Fall von std::unique_ptr
- Jetzt, da unsere Klasse zwischen Temporären und Nicht-Provisorischen unterscheiden kann, können wir dafür sorgen, dass die Move-Semantik korrekt funktioniert, sodass die unique_ptr
können nicht kopiert, aber verschoben werden, was bedeutet, dass std::unique_ptr
können legal in Standardcontainern gespeichert, sortiert usw. werden, während C++03's std::auto_ptr
kann nicht.
Jetzt betrachten wir die andere Verwendung von rvalue-Referenzen – perfekte Weiterleitung. Betrachten Sie die Frage der Bindung einer Referenz an eine Referenz.
std::string s;
std::string& ref = s;
(std::string&)& anotherref = ref; // usually expressed via template
Ich kann mich nicht erinnern, was C++03 dazu sagt, aber in C++0x ist der resultierende Typ beim Umgang mit rvalue-Referenzen entscheidend. Eine rvalue-Referenz auf einen Typ T, wobei T ein Referenztyp ist, wird zu einer Referenz vom Typ T.
(std::string&)&& ref // ref is std::string&
(const std::string&)&& ref // ref is const std::string&
(std::string&&)&& ref // ref is std::string&&
(const std::string&&)&& ref // ref is const std::string&&
Betrachten Sie die einfachste Vorlagenfunktion - min und max. In C++03 müssen Sie für alle vier Kombinationen von Konstante und Nicht-Konstante manuell überladen. In C++0x ist es nur eine Überladung. Kombiniert mit diversen Templates ermöglicht dies eine perfekte Weiterleitung.
template<typename A, typename B> auto min(A&& aref, B&& bref) {
// for example, if you pass a const std::string& as first argument,
// then A becomes const std::string& and by extension, aref becomes
// const std::string&, completely maintaining it's type information.
if (std::forward<A>(aref) < std::forward<B>(bref))
return std::forward<A>(aref);
else
return std::forward<B>(bref);
}
Ich habe den Rückgabetypabzug weggelassen, weil ich mich nicht erinnern kann, wie es auf Anhieb gemacht wurde, aber dieses min kann jede Kombination von lvalues, rvalues, const lvalues akzeptieren.

mmocny
Der Begriff für T&&
bei Verwendung mit Typabzug (z. B. für Perfect Forwarding) wird umgangssprachlich als a bezeichnet Weiterleitung Referenz. Der Begriff „Universal Reference“ wurde von Scott Meyers geprägt In diesem Artikelwurde aber später geändert.
Das liegt daran, dass es entweder ein R-Wert oder ein L-Wert sein kann.
Beispiele sind:
// template
template<class T> foo(T&& t) { ... }
// auto
auto&& t = ...;
// typedef
typedef ... T;
T&& t = ...;
// decltype
decltype(...)&& t = ...;
Weitere Diskussionen finden Sie in der Antwort für: Syntax für universelle Referenzen

Kurt Krückeberg
Eine rvalue-Referenz ist ein Typ, der sich mit einigen Ausnahmen ähnlich wie die gewöhnliche Referenz X& verhält. Der wichtigste ist, dass lvalues bei der Auflösung von Funktionsüberladungen lvalue-Referenzen im alten Stil bevorzugen, während rvalues die neuen rvalue-Referenzen bevorzugen:
void foo(X& x); // lvalue reference overload
void foo(X&& x); // rvalue reference overload
X x;
X foobar();
foo(x); // argument is lvalue: calls foo(X&)
foo(foobar()); // argument is rvalue: calls foo(X&&)
Was ist also ein rvalue? Alles, was kein lvalue ist. Ein lvalue ist ein Ausdruck, der sich auf einen Speicherort bezieht und es uns ermöglicht, die Adresse dieses Speicherorts über den &-Operator zu übernehmen.
Es ist fast einfacher zu verstehen, was rvalues zuerst mit einem Beispiel bewirken:
#include <cstring>
class Sample {
int *ptr; // large block of memory
int size;
public:
Sample(int sz=0) : ptr{sz != 0 ? new int[sz] : nullptr}, size{sz}
{
if (ptr != nullptr) memset(ptr, 0, sz);
}
// copy constructor that takes lvalue
Sample(const Sample& s) : ptr{s.size != 0 ? new int[s.size] :\
nullptr}, size{s.size}
{
if (ptr != nullptr) memcpy(ptr, s.ptr, s.size);
std::cout << "copy constructor called on lvalue\n";
}
// move constructor that take rvalue
Sample(Sample&& s)
{ // steal s's resources
ptr = s.ptr;
size = s.size;
s.ptr = nullptr; // destructive write
s.size = 0;
cout << "Move constructor called on rvalue." << std::endl;
}
// normal copy assignment operator taking lvalue
Sample& operator=(const Sample& s)
{
if(this != &s) {
delete [] ptr; // free current pointer
size = s.size;
if (size != 0) {
ptr = new int[s.size];
memcpy(ptr, s.ptr, s.size);
} else
ptr = nullptr;
}
cout << "Copy Assignment called on lvalue." << std::endl;
return *this;
}
// overloaded move assignment operator taking rvalue
Sample& operator=(Sample&& lhs)
{
if(this != &s) {
delete [] ptr; //don't let ptr be orphaned
ptr = lhs.ptr; //but now "steal" lhs, don't clone it.
size = lhs.size;
lhs.ptr = nullptr; // lhs's new "stolen" state
lhs.size = 0;
}
cout << "Move Assignment called on rvalue" << std::endl;
return *this;
}
//...snip
};
Die Konstruktor- und Zuweisungsoperatoren wurden mit Versionen überladen, die Rvalue-Referenzen annehmen. Rvalue-Referenzen ermöglichen es einer Funktion, zur Kompilierzeit (über die Überladungsauflösung) unter der Bedingung „Werde ich von einem Lvalue oder einem Rvalue aufgerufen?“ zu verzweigen. Dadurch konnten wir effizientere Konstruktoren und Zuweisungsoperatoren oben erstellen, die Ressourcen verschieben, anstatt sie zu kopieren.
Der Compiler verzweigt sich automatisch zur Kompilierzeit (abhängig davon, ob er für einen lvalue oder einen rvalue aufgerufen wird) und wählt aus, ob der Move-Konstruktor oder der Move-Zuweisungsoperator aufgerufen werden soll.
Zusammenfassend: rvalue-Referenzen ermöglichen Bewegungssemantik (und perfekte Weiterleitung, die im Artikellink unten besprochen wird).
Ein praktisches, leicht verständliches Beispiel ist die Klassenvorlage std::unique_ptr. Da ein unique_ptr das exklusive Eigentum an seinem zugrunde liegenden Rohzeiger behält, können die von unique_ptr nicht kopiert werden. Das würde ihre Invariante des ausschließlichen Eigentums verletzen. Sie haben also keine Kopierkonstruktoren. Aber sie haben Bewegungskonstruktoren:
template<class T> class unique_ptr {
//...snip
unique_ptr(unique_ptr&& __u) noexcept; // move constructor
};
std::unique_ptr<int[] pt1{new int[10]};
std::unique_ptr<int[]> ptr2{ptr1};// compile error: no copy ctor.
// So we must first cast ptr1 to an rvalue
std::unique_ptr<int[]> ptr2{std::move(ptr1)};
std::unique_ptr<int[]> TakeOwnershipAndAlter(std::unique_ptr<int[]> param,\
int size)
{
for (auto i = 0; i < size; ++i) {
param[i] += 10;
}
return param; // implicitly calls unique_ptr(unique_ptr&&)
}
// Now use function
unique_ptr<int[]> ptr{new int[10]};
// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(\
static_cast<unique_ptr<int[]>&&>(ptr), 10);
cout << "output:\n";
for(auto i = 0; i< 10; ++i) {
cout << new_owner[i] << ", ";
}
output:
10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
static_cast<unique_ptr<int[]>&&>(ptr)
erfolgt in der Regel mit std::bewegen
// first cast ptr from lvalue to rvalue
unique_ptr<int[]> new_owner = TakeOwnershipAndAlter(std::move(ptr),0);
Ein ausgezeichneter Artikel, der all dies und mehr erklärt (z. B. wie R-Werte eine perfekte Weiterleitung ermöglichen und was das bedeutet) mit vielen guten Beispielen ist der von Thomas Becker C++ Rvalue-Referenzen erklärt. Dieser Beitrag stützte sich stark auf seinen Artikel.
Eine kürzere Einführung ist Eine kurze Einführung in Rvalue-Referenzen von Stroutrup, et. Al
9952300cookie-checkWas bedeutet T&& (doppeltes kaufmännisches Und) in C++11?yes
Ich habe dies in die C++-FAQ aufgenommen, da ich mir sicher bin, dass es in Zukunft öfter auftauchen wird.
– GManNickG
30. März 2011 um 3:50 Uhr
verwandte Frage zur Move-Semantik
– fredoverflow
30. März 2011 um 6:56 Uhr
Sie können mit Google danach suchen, Sie müssen Ihren Satz nur in Anführungszeichen setzen: google.com/#q=”T%26%26” hat jetzt deine Frage als ersten Treffer.
:)
– sbi
22. Juni 2011 um 7:46 Uhr
Ich habe oben drei Stackoverflow-Fragen erhalten, die in Google nach “c++ two kaufmännische Und-Parameter” gesucht haben, und Ihre war die erste. Sie müssen also nicht einmal Satzzeichen verwenden, wenn Sie “zwei kaufmännische Und-Parameter” buchstabieren können.
– sergiol
14. August 2015 um 7:28 Uhr
@sergiol und @sbi: Google wird immer besser: Eine Suche nach
c++ T&& type
(ohne Anführungszeichen oder irgendetwas) bringt dies als ersten Treffer.– Peter Cordes
4. Oktober 2016 um 22:13 Uhr