Freigeben über


Rvalu-Verweis-Deklarator: &&

Enthält einen Verweis auf einen rvalue-Ausdruck.

type-id && cast-expression

Hinweise

Rvalue-Verweise ermöglichen Ihnen, ein lvalue von einem rvalue zu unterscheiden.Lvalue- und rvalue-Verweise sind syntaktisch und semantisch ähnlich, folgen jedoch etwas unterschiedlichen Regeln.Weitere Informationen über lvalues und rvalue finden Sie unter Lvalues und Rvalue.Weitere Informationen über lvalue-Verweise finden Sie unter Lvalue-Bezugsdeklarator: &..

Die folgenden Abschnitte beschreiben, wie rvalue-Verweise die Implementierung von Bewegungssemantik und perfekte Weiterleitung unterstützen.

Semantiken verschieben

Rvalue-Verweise unterstützen die Implementierung von Verschiebesemantik, die die Leistung Ihrer Anwendungen bedeutend erhöhen kann.Das Verschieben von Semantiken ermöglicht es Ihnen, einen Code zu schreiben, der Ressourcen (wie dynamisch belegter Speicher) von einem Objekt zu einem anderen überträgt.Das Verschieben von Semantiken funktioniert, da es das Übertragen von Ressourcen von temporären Objekten ermöglicht, die nicht an anderer Stelle im Programm referenziert werden können.

Um Verschiebesemantik zu implementieren, stellen Sie i. d. R. einen Verschiebekonstruktor und optional einen Bewegungszuweisungsoperator (operator=) für die Klasse bereit.Kopieren- und Zuweisungsvorgänge, deren Quellen rvalues sind, nutzen dann automatisch den Vorteil der Verschiebesemantik.Im Gegensatz zum Standardkopierkonstruktor stellt der Compiler keinen standardmäßigen Verschiebekonstruktor bereit.Weitere Informationen dazu, wie man einen Verschiebekonstruktor schreibt und wie er in einer Anwendung verwendet wird finden Sie unter Gewusst wie: Schreiben Sie einen Verschiebungskonstruktor.

Sie können auch gewöhnliche Funktionen und Operatoren überladen, um Verschiebesemantik zu nutzen.Visual C++ 2010 führt Verschiebesemantik in der Standardvorlagenbibliothek (STL) ein.Beispielsweise implementiert die string-Klasse Vorgänge, die Verschiebesemantik ausführt.Betrachten Sie das folgende Beispiel, in dem mehrere Zeichenfolgen verkettet sind 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 C++ 2010 ordnet jeder Aufruf des operator+ ein neues temporäres string-Objekt zu (einen rvalue) und gibt ihn zurück.operator+ kann keine Zeichenfolge der anderen anfügen, da er nicht weiß, ob die Quellzeichenfolgen lvalues oder rvalues sind.Wenn die Quellzeichenfolgen beide lvalues sind, werden sie möglicherweise an anderer Stelle im Programm referenziert und dürfen deshalb nicht geändert werden.Indem Sie rvalue-Verweise verwenden, kann operator+ so geändert werden, dass rvalues, die nicht an anderer Stelle im Programm verwiesen werden können übernommen werden.Daher kann operator+ jetzt eine Zeichenfolge an eine andere anfügen.Dadurch kann die Anzahl dynamischer Speicherbelegungen beträchtlich reduziert werden, die die string-Klasse ausführen muss.Weitere Informationen über die string-Klasse finden Sie unter basic_string Class.

Das Verschieben von Semantiken ist auch hilfreich, wenn der Compiler keine Rückgabewert-Optimierung (RVO) oder Benannte Rückgabewert-Optimierung (NRVO) verwenden kann.In diesen Fällen ruft der Compiler den Verschiebekonstruktor auf, wenn der Typ diesen definiert.Weitere Informationen über benannte Rückgabewert-Optimierung, finden Sie unter Benannte Rückgabewert-Optimierung in Visual C++ 2005

Das Beispiel beim 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 Objekt vector Speicher für seine Elemente neu zuteilen, und dann jedes Element an einen anderen Speicherort kopieren, um für das eingefügte Element Platz zu schaffen.Wenn ein Einfügevorgang ein Element kopiert, erstellt er ein neues Element, ruft den Kopierkonstruktor auf, um die Daten aus dem vorherigen Element in das neue Element zu kopieren, und zerstört dann das vorherige Element.Das Verschieben von Semantiken ermöglicht es Ihnen, Objekte direkt zu verschieben, ohne teure Speicherbelegung und Kopiervorgänge ausführen zu müssen.

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

Weitere Informationen über die Einführung von Verschiebesemantik in STL in Visual C++ 2010 finden Sie unter C++-Standardbibliothek-Referenz.

Perfect Forwarding

Perfect Forwarding reduziert die Anforderung für überladene Funktionen und hilft, das Weiterleitungsproblem zu vermeiden.Die Weiterleitungsproblem kann auftreten, wenn Sie eine generische Funktion schreiben, welche Verweise als ihre Parameter abruft und sie diese Parameter an eine andere Funktion übergibt (oder weiterleitet).Wenn beispielsweise die generische Funktion einen Parameter vom Typ const T& akzeptiert, kann die aufgerufene Funktion den Wert dieses Parameters nicht ändern.Wenn die generische Funktion einen Parameter vom Typ T& wählt, kann die Funktion nicht aufgerufen werden, indem ein rvalue (z. B. ein temporäres Objekt oder Ganzzahlliteral) verwendet wird.

Um dieses Problem zu lösen, müssen Sie normalerweise überladene Versionen der generischen Funktion bereitstellen, die T& und const T& für jeden ihrer Parameter akzeptieren.Daher erhöht sich die Anzahl der überladenen Funktionen exponentiell mit der Anzahl von Parametern.Mit Rvalue-Verweisen können Sie auch eine Version einer Funktion schreiben, die beliebige Argumente akzeptiert und diese an eine andere Funktion weiterleitet, als wäre die andere Funktion direkt aufgerufen worden.

Betrachten Sie das folgende Beispiel, in dem vier Typen, W, X, Y und Z deklariert werden.Der Konstruktor für jeden Typ akzeptiert eine andere Kombination von const und Nicht-const-L-Wert-Verweisen 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 beschrieben:

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 keinen gültigen Aufruf der factory-Funktion, da factory lvalue-Verweise übernimmt, die als Parameter änderbar sind, sie wird jedoch unter Verwendung von rvalues aufgerufen:

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 rvalu-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 Funktion main, welche die überarbeitete Funktion factory verwendet, um Instanzen der Klassen W, X, Y und Z zu erstellen.Die überarbeitete factory-Funktion leitet seine 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;
}

Zusätzliche Eigenschaften von rvalu-Verweisen

Sie können eine Funktion überladen, um einen lvalue-Verweis und einen rvalue-Verweis anzunehmen.

Wenn Sie eine Funktion überladen, um einen const lvalue Verweis oder einen rvalu-Verweis zu übernehmen, können Sie Code schreiben, der zwischen nicht veränderbaren Objects (lvalues) und änderbaren temporären Werten (rvalues) unterscheidet.Sie können ein Objekt an eine Funktion übergeben, die einen rvalue-Verweis verwendet, es sei denn, das Objekt ist als const gekennzeichnet.Das folgende Beispiel zeigt die Funktion f, welche überladen ist, um einen lvalue- und rvalue-Verweis abzurufen.Die main-Funktion ruft f sowohl mit lvalue- als auch mit rvalue-Verweisen 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 cannot 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 folgende Ausgabe:

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

In diesem Beispiel übergibt der erste Aufruf an f eine lokale Variable (ein lvalue) als Argument.Der zweite Aufruf von f führt ein temporäres Objekt als Argument.Da das temporäre Objekt nicht an anderer Stelle im Programm verwiesen werden kann, bindet sich der Aufruf an die überladene Version von f, die einen rvalue-Verweis akzeptiert, der das Objekt ändern kann.

Der Compiler behandelt einen benannten rvalue-Verweis als L-Wert und einen unbenannten rvalue-Verweis als R-Wert.

Wenn Sie eine Funktion schreiben, die einen rvalu-Verweis als Parameter akzeptiert, wird dieser Parameter als lvalue im Text der Funktion behandelt.Der Compiler behandelt einen benannten rvalu-Verweis als L-Wert, da auf ein benanntes Objekt von mehreren Teilen eines Programms verwiesen werden kann. Es wäre gefährlich, mehrere Teile eines Programms Ressourcen aus diesem Objekt ändern oder entfernen zu lassen.Wenn z. B. mehrere Teile eines Programms versuchen, Ressourcen zum gleichen Objekt zu übertragen, wird nur der erste Teil die Ressource erfolgreich übertragen.

Im folgenden Beispiel wird die g-Funktion gezeigt, die überladen wird, um einen lvalu-Verweis und einen rvalue-Verweis zu übernehmen.Die Funktion f ruft einen rvalue-Verweis als ihren Parameter (einen benannten rvalue-Verweis) ab und gibt einen rvalue-Verweis (einen unbenannten rvalue-Verweis) zurück.Im Aufruf von g von f, wählt Überladungsauflösung die Version von g aus, die einen lvalue-Verweis verwendet, da der Text von f den Parameter als lvalue behandelt.Im Aufruf von g von main, wählt Überladungsauflösung die Version von g aus, die einen rvalu-Verweis akzeptiert, da f einen rvalu-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 block;
}

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

Dieses Beispiel erzeugt folgende Ausgabe:

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

In diesem Beispiel übergibt die main-Funktion einen rvalue 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 ein lvalue-Verweis in einen rvalue-Verweis umwandeln.

Die STL-Funktion std::move ermöglicht es Ihnen, ein Objekt zu einem rvalu-Verweis auf dieses Objekt zu konvertieren.Alternativ können Sie das static_cast-Schlüsselwort verwenden, um ein lvalue zu einem rvalu-Verweis, wie im folgenden Beispiel gezeigt, umzuwandeln:

// 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 folgende Ausgabe:

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

 

Funktionsvorlagen leiten ihren Vorlagenargument Typen ab, und verwenden dann Verweisreduzierungs-Regeln.

Es ist üblich, eine Funktionsvorlage zu schreiben, die die Parameter an eine andere Funktion übergibt (oder weiterleitet).Es ist wichtig zu wissen, wie die Ableitung des Vorlagentyps für Funktionsvorlagen, die rvalu-Verweise akzeptieren, funktioniert.

Wenn das Funktionsargument ein rvalue ist, geht der Compiler davon aus, dass das Argument ein rvalue-Verweis ist.Wenn Sie beispielsweise einen rvalu-Verweis in ein Objekt vom Typ X auf eine Vorlagenfunktion führen, die T&&-Typ als Parameter akzeptiert, folgert Vorlagenargumentfolgerung, dass TX sein muss.Daher hat der Parameter den Typ X&&.Wenn das Funktionsargument ein lvalue oder const lvalue ist, geht der Compiler davon aus, das der Typ ein lvalue-Verweis oder const lvalue-Verweis dieses Typs ist.

Im folgenden Beispiel wird eine Strukturvorlage deklariert und dann spezialisiert für verschiedene Verweistypen.Die print_type_and_value-Funktion nimmt einen rvalu-Verweis als Parameter und leitet ihn an die entsprechende spezialisierte Version der S::print-Methode weiter.Die main-Funktion zeigt die verschiedenen Methoden, 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 folgende Ausgabe:

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

Um die einzelnen Aufrufe der print_type_and_value-Funktion aufzulösen, führt der Compiler zunächst eine Ableitung der Vorlagenargumente aus.Der Compiler wendet dann den Verweis an, der Regeln reduziert, wenn er die abgeleiteten Vorlagenargumente für die Parametertypen ersetzt.Zum Beispiel wird die Übergabe der lokalen Variablen s1 zur print_type_and_value-Funktion den Compiler bewirken, dass der Complier die nächste Funktionssignatur erstellt:

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

Der Compiler verwendet den Verweisreduzierungsregeln, um die Signatur wie folgt 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 den Vorlagenargumenttypabzug zusammen:

Breitschrift

Reduzierter Typ

T& &

T&

T& &&

T&

T&& &

T&

T&& &&

T&&

Vorlagenargumentableitung ist ein wichtiges Element für die Implementierung der perfekten Weiterleitung.Der Abschnitt zur perfekten Weiterleitung, die bereits besprochen wurde, beschreibt die perfekte Weiterleitung im Detail.

Zusammenfassung

Rvalue-Verweise unterscheiden lvalues von rvalues.Sie können Ihnen helfen, die Leistung der Anwendungen zu verbessern, indem die Anforderung für unnötige Speicherbelegungen und Kopiervorgänge beseitigt wird.Sie können damit auch eine Version einer Funktion schreiben, die beliebige Argumente akzeptiert und diese an eine andere Funktion weiterleitet, als wäre die andere Funktion direkt aufgerufen wurde.

Siehe auch

Aufgaben

Gewusst wie: Schreiben Sie einen Verschiebungskonstruktor

Referenz

Ausdrücke mit unären Operatoren

Lvalue-Bezugsdeklarator: &.

Lvalues und Rvalue

move

forward

Weitere Ressourcen

C++-Standardbibliothek-Referenz