Rvalue-Referenz-Deklarator: &&

Enthält einen Verweis auf einen rvalue-Ausdruck.

Syntax

rvalue-reference-type-id:
type-specifier-seq&&attribute-specifier-seqoptptr-abstract-declaratoropt

Hinweise

Mit rvalue-Verweisen haben Sie die Möglichkeit, einen lvalue von einem rvalue zu unterscheiden. Lvalue-Bezüge und Rvalue-Bezüge sind syntaktisch und semantisch ähnlich, aber sie folgen leicht unterschiedlichen Regeln. Weitere Informationen zu Lvaluen und Rvalues finden Sie unter Lvalues und Rvalues. Weitere Informationen zu lvalue-Verweisen finden Sie unter Lvalue Reference Declarator: &.

In den folgenden Abschnitten wird beschrieben, wie Rvalue-Verweise die Implementierung der Bewegungssemantik und der perfekten Weiterleitung unterstützen.

Verschiebesemantik

Rvalue-Verweise unterstützen die Implementierung der Bewegungssemantik, wodurch die Leistung Ihrer Anwendungen erheblich erhöht werden kann. Mithilfe der Verschiebesemantik können Sie Code schreiben, der Ressourcen (wie dynamisch zugeordneten Speicher) von einem Objekt zu einem anderen überträgt. Verschieben der Semantik funktioniert, da es das Übertragen von Ressourcen aus temporären Objekten ermöglicht: auf solche, auf die nicht an anderer Stelle im Programm verwiesen werden kann.

Um die Bewegungssemantik zu implementieren, stellen Sie in der Regel einen Verschiebungskonstruktor und optional einen Verschiebungszuweisungsoperator (operator=) für Ihre Klasse bereit. Kopier- und Zuordnungsvorgänge, deren Quellen rvalues sind, nutzen dann automatisch die Verschiebesemantik. Im Gegensatz zum Standardkopierkonstruktor stellt der Compiler keinen Standardverschiebungskonstruktor bereit. Weitere Informationen zum Schreiben und Verwenden eines Verschiebungskonstruktors finden Sie unter Verschieben von Konstruktoren und Verschieben von Zuordnungsoperatoren.

Sie können auch gewöhnliche Funktionen und Operatoren überladen, um die Verschiebesemantik zu nutzen. Visual Studio 2010 führt das Verschieben der Semantik in die C++-Standardbibliothek ein. Beispielsweise implementiert die string Klasse Vorgänge, die die Bewegungssemantik verwenden. Betrachten Sie das folgende Beispiel, in dem mehrere Zeichenfolgen verkettet werden und das Ergebnis ausgegeben wird:

// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

int main()
{
   string s = string("h") + "e" + "ll" + "o";
   cout << s << endl;
}

Vor Visual Studio 2010 wird für jeden Aufruf ein operator+ neues temporäres string Objekt zugeordnet und zurückgegeben (ein Wert). operator+ eine Zeichenfolge kann nicht an die andere angefügt werden, da sie nicht weiß, ob es sich bei den Quellzeichenfolgen um lvalues oder rvalues handelt. Wenn die Quellzeichenfolgen beide Werte sind, werden sie möglicherweise an anderer Stelle im Programm referenziert und dürfen daher nicht geändert werden. Sie können ändern operator+ , um Werte zu übernehmen, indem Sie Rvalue-Verweise verwenden, auf die nicht an anderer Stelle im Programm verwiesen werden kann. Mit dieser Änderung operator+ können Sie nun eine Zeichenfolge an eine andere anfügen. Durch die Änderung wird die Anzahl der dynamischen Speicherzuweisungen, die die string Klasse vornehmen muss, erheblich reduziert. Weitere Informationen zur string Klasse finden Sie unter basic_string "Klasse".

Verschieben der Semantik hilft auch, wenn der Compiler keine Rückgabewertoptimierung (Return Value Optimization, RVO) oder benannte Rückgabewertoptimierung (Named Return Value Optimization, NRVO) verwenden kann. In diesen Fällen ruft der Compiler den Verschiebekonstruktor auf, wenn der Typ diesen definiert.

Das Beispiel zum Einfügen eines Elements in ein vector-Objekt hilft Ihnen, die Verschiebesemantik besser zu verstehen. Wenn die Kapazität des vector Objekts überschritten wird, muss das vector Objekt genügend Arbeitsspeicher für seine Elemente zuordnen und dann jedes Element an einen anderen Speicherort kopieren, um Platz für das eingefügte Element zu schaffen. Wenn ein Einfügevorgang ein Element kopiert, wird zunächst ein neues Element erstellt. Anschließend wird der Kopierkonstruktor aufgerufen, um die Daten aus dem vorherigen Element in das neue Element zu kopieren. Schließlich zerstört es das vorherige Element. Mit der Verschiebensemantik können Sie Objekte direkt verschieben, ohne teure Speicherzuordnungs- und Kopiervorgänge vornehmen zu müssen.

Um die Verschiebesemantik im vector-Beispiel zu nutzen, können Sie einen Bewegungskonstruktor schreiben, um Daten von einem Objekt in ein anderes zu verschieben.

Weitere Informationen zur Einführung der Verschiebensemantik in die C++-Standardbibliothek in Visual Studio 2010 finden Sie unter C++-Standardbibliothek.

Perfekte Weiterleitung

Perfekte Weiterleitung reduziert die Notwendigkeit überladener Funktionen und hilft, das Weiterleitungsproblem zu vermeiden. Das Weiterleitungsproblem kann auftreten, wenn Sie eine generische Funktion schreiben, die Verweise als Parameter verwendet. Wenn diese Parameter an eine andere Funktion übergeben (oder weiterleiten), z. B. wenn ein Parameter vom Typ const T&verwendet wird, kann die aufgerufene Funktion den Wert dieses Parameters nicht ändern. Wenn die generische Funktion einen Parameter vom Typ T&verwendet, kann die Funktion nicht mithilfe eines Werts aufgerufen werden (z. B. ein temporäres Objekt oder ein ganzzahliges Literal).

Um dieses Problem zu lösen, müssen Sie normalerweise überladene Versionen der generischen Funktion bereitstellen, die sowohl T& als auch const T& für jeden ihrer Parameter akzeptieren. Dadurch erhöht sich die Anzahl von überladenen Funktionen exponentiell mit der Anzahl von Parametern. Mithilfe von Rvalue-Verweisen können Sie eine Version einer Funktion schreiben, die beliebige Argumente akzeptiert. Dann kann diese Funktion sie an eine andere Funktion weiterleiten, als ob die andere Funktion direkt aufgerufen wurde.

Betrachten Sie das folgende Beispiel, in dem vier Typen, W, X, Y und Z, deklariert werden. Der Konstruktor für jeden Typ verwendet eine andere Kombination von const und nicht-lvalue-Verweisenconst als Parameter.

struct W
{
   W(int&, int&) {}
};

struct X
{
   X(const int&, int&) {}
};

struct Y
{
   Y(int&, const int&) {}
};

struct Z
{
   Z(const int&, const int&) {}
};

Angenommen, Sie möchten eine generische Funktion schreiben, die Objekte generiert. Im folgenden Beispiel wird eine Möglichkeit zum Schreiben dieser Funktion dargestellt:

template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
   return new T(a1, a2);
}

Im folgenden Beispiel wird ein gültiger Aufruf der factory-Funktion veranschaulicht:

int a = 4, b = 5;
W* pw = factory<W>(a, b);

Das folgende Beispiel enthält jedoch keinen gültigen Aufruf der factory Funktion. Dies liegt daran, dass factory lvalue-Verweise verwendet werden, die als Parameter geändert werden können, aber mithilfe von Rvalues aufgerufen werden:

Z* pz = factory<Z>(2, 2);

Um dieses Problem zu lösen, müssen Sie normalerweise eine überladene Version der factory-Funktion für jede Kombination von A&- und const A&-Parametern erstellen. Mit rvalue-Verweisen können Sie eine Version der factory-Funktion schreiben, wie im folgenden Beispiel gezeigt:

template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
   return new T(std::forward<A1>(a1), std::forward<A2>(a2));
}

In diesem Beispiel werden rvalue-Verweise als Parameter für die factory-Funktion verwendet. Der Zweck der std::forward Funktion besteht darin, die Parameter der Factoryfunktion an den Konstruktor der Vorlagenklasse weiterzuleiten.

Das folgende Beispiel zeigt die main-Funktion, die die überarbeitete factory-Funktion verwendet, um Instanzen der Klassen W, X, Y und Z zu erstellen. Die überarbeitete factory-Funktion leitet ihre Parameter (entweder lvalues oder rvalues) an den entsprechenden Klassenkonstruktor weiter.

int main()
{
   int a = 4, b = 5;
   W* pw = factory<W>(a, b);
   X* px = factory<X>(2, b);
   Y* py = factory<Y>(a, 2);
   Z* pz = factory<Z>(2, 2);

   delete pw;
   delete px;
   delete py;
   delete pz;
}

Eigenschaften von Rvalue-Verweisen

Sie können eine Funktion überladen, um einen Lvalue-Verweis und einen Rvalue-Verweis zu verwenden.

Indem Sie eine Funktion überladen, um einen const Lvalue-Verweis oder einen Rvalue-Verweis zu übernehmen, können Sie Code schreiben, der zwischen nicht modifizierbaren Objekten (lvalues) und modifizierbaren temporären Werten (Rvalues) unterscheidet. Sie können ein Objekt an eine Funktion übergeben, die einen Wertverweis verwendet, es sei denn, das Objekt ist als constmarkiert. Das folgende Beispiel zeigt die f-Funktion, die überladen ist, damit ein lvalue-Verweis und ein rvalue-Verweis akzeptiert wird. Die main-Funktion ruft f sowohl mit lvalue als auch mit rvalue auf.

// reference-overload.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void f(const MemoryBlock&)
{
   cout << "In f(const MemoryBlock&). This version can't modify the parameter." << endl;
}

void f(MemoryBlock&&)
{
   cout << "In f(MemoryBlock&&). This version can modify the parameter." << endl;
}

int main()
{
   MemoryBlock block;
   f(block);
   f(MemoryBlock());
}

Dieses Beispiel erzeugt die folgende Ausgabe:

In f(const MemoryBlock&). This version can't modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.

In diesem Beispiel übergibt der erste Aufruf von f eine lokale Variable (lvalue) als Argument. Der zweite Aufruf von f übergibt ein temporäres Objekt als Argument. Da auf das temporäre Objekt nicht an anderer Stelle des Programms verwiesen werden kann, bindet der Aufruf an die überladene Version, von f der ein Rvalue-Verweis verwendet wird, der frei ist, das Objekt zu ändern.

Der Compiler behandelt einen benannten Rvalue-Verweis als lvalue und einen unbenannten Rvalue-Verweis als Rvalue.

Funktionen, die einen Rvalue-Verweis als Parameter verwenden, behandeln den Parameter als lvalue im Textkörper der Funktion. Der Compiler behandelt einen benannten Rvalue-Verweis als lvalue. Dies liegt daran, dass auf ein benanntes Objekt von mehreren Teilen eines Programms verwiesen werden kann. Es ist gefährlich, mehreren Teilen eines Programms das Ändern oder Entfernen von Ressourcen aus diesem Objekt zu ermöglichen. Wenn beispielsweise mehrere Teile eines Programms versuchen, Ressourcen aus demselben Objekt zu übertragen, ist nur die erste Übertragung erfolgreich.

Das folgende Beispiel zeigt die g-Funktion, die überladen ist, damit ein lvalue-Verweis und ein rvalue-Verweis akzeptiert wird. Die f-Funktion akzeptiert einen rvalue-Verweis als ihren Parameter (einen benannten rvalue-Verweis) und gibt einen rvalue-Verweis (einen unbenannten rvalue-Verweis) zurück. Im Aufruf von g von f wählt die Überladungsauflösung die Version von g aus, die einen lvalue-Verweis akzeptiert, da der Text von f den Parameter als lvalue behandelt. Im Aufruf von g von main wählt die Überladungsauflösung die Version von g aus, die einen rvalue-Verweis akzeptiert, da f einen rvalue-Verweis zurückgibt.

// named-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void g(const MemoryBlock&)
{
   cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
   cout << "In g(MemoryBlock&&)." << endl;
}

MemoryBlock&& f(MemoryBlock&& block)
{
   g(block);
   return move(block);
}

int main()
{
   g(f(MemoryBlock()));
}

Dieses Beispiel erzeugt die folgende Ausgabe:

In g(const MemoryBlock&).
In g(MemoryBlock&&).

Im Beispiel übergibt die main Funktion einen Wert an f. Der Text von f behandelt den benannten Parameter als lvalue. Der Aufruf von f zu g bindet den Parameter an einen lvalue-Verweis (die erste überladene Version von g).

  • Sie können einen Wert in einen Rvalue-Verweis umwandeln.

Mit der C++-Standardbibliotheksfunktion std::move können Sie ein Objekt in einen Rvalue-Verweis auf dieses Objekt konvertieren. Sie können auch die static_cast Schlüsselwort (keyword) verwenden, um einen Wert in einen Rvalue-Verweis zu umwandeln, wie im folgenden Beispiel gezeigt:

// cast-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void g(const MemoryBlock&)
{
   cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
   cout << "In g(MemoryBlock&&)." << endl;
}

int main()
{
   MemoryBlock block;
   g(block);
   g(static_cast<MemoryBlock&&>(block));
}

Dieses Beispiel erzeugt die folgende Ausgabe:

In g(const MemoryBlock&).
In g(MemoryBlock&&).

Funktionsvorlagen leiten ihre Vorlagenargumenttypen ab und verwenden dann Die Regeln zum Reduzieren von Verweisen.

Eine Funktionsvorlage, die ihre Parameter an eine andere Funktion übergibt (oder weiterleitet), ist ein gängiges Muster. Es ist wichtig zu verstehen, wie der Vorlagentypabzug für Funktionsvorlagen funktioniert, die Verweise auf rvalue verwenden.

Wenn das Funktionsargument ein rvalue ist, geht der Compiler davon aus, dass das Argument ein rvalue-Verweis ist. Angenommen, Sie übergeben einen Rvalue-Verweis auf ein Objekt vom Typ X an eine Funktionsvorlage, die Typ T&& als Parameter verwendet. Der Abzug des Vorlagenarguments TXwird abgeleitet, sodass der Parameter den Typ X&&hat. Wenn das Funktionsargument ein lvalue oder const lvalue ist, leitet der Compiler seinen Typ als lvalue-Bezug oder const lvalue-Bezug dieses Typs ab.

Im folgenden Beispiel wird eine Strukturvorlage deklariert und dann für verschiedene Verweistypen spezialisiert. Die print_type_and_value-Funktion akzeptiert einen rvalue-Verweis als Parameter und leitet ihn an die entsprechende spezialisierte Version der S::print-Methode weiter. Die main-Funktion zeigt die verschiedenen Möglichkeiten, die S::print-Methode aufzurufen.

// template-type-deduction.cpp
// Compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

template<typename T> struct S;

// The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter.

template<typename T> struct S<T&> {
   static void print(T& t)
   {
      cout << "print<T&>: " << t << endl;
   }
};

template<typename T> struct S<const T&> {
   static void print(const T& t)
   {
      cout << "print<const T&>: " << t << endl;
   }
};

template<typename T> struct S<T&&> {
   static void print(T&& t)
   {
      cout << "print<T&&>: " << t << endl;
   }
};

template<typename T> struct S<const T&&> {
   static void print(const T&& t)
   {
      cout << "print<const T&&>: " << t << endl;
   }
};

// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{
   S<T&&>::print(std::forward<T>(t));
}

// This function returns the constant string "fourth".
const string fourth() { return string("fourth"); }

int main()
{
   // The following call resolves to:
   // print_type_and_value<string&>(string& && t)
   // Which collapses to:
   // print_type_and_value<string&>(string& t)
   string s1("first");
   print_type_and_value(s1);

   // The following call resolves to:
   // print_type_and_value<const string&>(const string& && t)
   // Which collapses to:
   // print_type_and_value<const string&>(const string& t)
   const string s2("second");
   print_type_and_value(s2);

   // The following call resolves to:
   // print_type_and_value<string&&>(string&& t)
   print_type_and_value(string("third"));

   // The following call resolves to:
   // print_type_and_value<const string&&>(const string&& t)
   print_type_and_value(fourth());
}

Dieses Beispiel erzeugt die folgende Ausgabe:

print<T&>: first
print<const T&>: second
print<T&&>: third
print<const T&&>: fourth

Um jeden Aufruf der print_type_and_value Funktion aufzulösen, führt der Compiler zuerst einen Vorlagenargumentabzug durch. The compiler then applies reference collapsing rules when it replaces the parameter types with the deduced template arguments. Zum Beispiel führt die Übergabe der lokalen Variable s1 an die print_type_and_value-Funktion dazu, dass der Compiler die folgende Funktionssignatur erstellt:

print_type_and_value<string&>(string& && t)

Der Compiler verwendet Referenz-Reduzierungsregeln, um die Signatur zu reduzieren:

print_type_and_value<string&>(string& t)

Diese Version der print_type_and_value-Funktion leitet dann den Parameter an die richtige spezialisierte Version der S::print-Methode weiter.

Die folgende Tabelle fasst die Verweisreduzierungsregeln für die Ableitung des Vorlagenargumenttyps zusammen:

Erweiterter Typ Reduzierter Typ
T& & T&
T& && T&
T&& & T&
T&& && T&&

Vorlagenargumentableitung ist ein wichtiger Bestandteil der Implementierung der perfekten Weiterleitung. Der Abschnitt "Perfect Forwarding" beschreibt die perfekte Weiterleitung ausführlicher.

Zusammenfassung

Rvalue-Verweise unterscheiden lvalues von rvalues. Um die Leistung Ihrer Anwendungen zu verbessern, können sie die Notwendigkeit unnötiger Speicherzuweisungen und Kopiervorgänge vermeiden. Außerdem können Sie eine Funktion schreiben, die beliebige Argumente akzeptiert. Diese Funktion kann sie an eine andere Funktion weiterleiten, als ob die andere Funktion direkt aufgerufen wurde.

Siehe auch

Ausdrücke mit unären Operatoren
Lvalue-Referenz-Deklarator: &
Lvalues und Rvalues
Verschieben von Konstruktoren und Verschieben von Zuordnungsoperatoren (C++)
C++-Standardbibliothek