Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
rvalue-Verweisdeklarator:
Enthält einen Verweis auf einen rvalue-Ausdruck.
Syntax
rvalue-reference-type-id
:
type-specifier-seq
&&
attribute-specifier-seq
opt ptr-abstract-declarator
opt
Hinweise
Mit rvalue-Verweisen haben Sie die Möglichkeit, einen lvalue von einem rvalue zu unterscheiden. lvalue- und rvalue-Verweise sind syntaktisch und semantisch ähnlich, folgen jedoch etwas unterschiedlichen Regeln. Weitere Informationen zu lvalues und rvalues finden Sie hier. Weitere Informationen zu lvalue-Verweisen finden Sie unter lvalue-Referenzdeklarator: &.
In den folgenden Abschnitten wird beschrieben, wie rvalue-Verweise die Implementierung der Move-Semantik und die perfekte Weiterleitung unterstützen.
Verschiebesemantik
rvalue-Verweise unterstützen die Implementierung der Move-Semantik, die die Leistung Ihrer Anwendungen entscheidend verbessern kann. Mithilfe der Verschiebesemantik können Sie Code schreiben, der Ressourcen (wie dynamisch zugeordneten Speicher) von einem Objekt zu einem anderen überträgt. Die Move-Semantik funktioniert, da sie die Übertragung von Ressourcen aus temporären Objekten ermöglicht (also aus Objekten, auf die nicht an anderer Stelle im Programm verwiesen werden kann).
Zur Implementierung der Move-Semantik stellen Sie in der Regel einen Bewegungskonstruktor und optional einen Bewegungszuweisungsoperator (operator=
) für Ihre Klasse bereit. Kopier- und Zuordnungsvorgänge, deren Quellen rvalues sind, nutzen dann automatisch die Verschiebesemantik. Anders als beim standardmäßigen Kopierkonstruktor stellt der Compiler keinen standardmäßigen Bewegungskonstruktor bereit. Weitere Informationen zum Schreiben und Verwenden eines Bewegungskonstruktors finden Sie unter Bewegungskonstruktoren und Bewegungszuweisungsoperatoren.
Sie können auch gewöhnliche Funktionen und Operatoren überladen, um die Verschiebesemantik zu nutzen. Visual Studio 2010 führt die Move-Semantik in die C++-Standardbibliothek ein. Beispielsweise implementiert die string
-Klasse Vorgänge, die Move-Semantik 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 ordnet jeder Aufruf von operator+
ein neues temporäres string
-Objekt (rvalue) zu und gibt es zurück. operator+
kann keine Zeichenfolgen aneinander anfügen, da nicht bekannt ist, ob es sich bei den Quellzeichenfolgen um lvalues oder um rvalues handelt. Wenn beide Quellzeichenfolgen lvalues sind, wird möglicherweise an anderer Stelle im Programm auf sie verwiesen. Daher dürfen sie nicht geändert werden. Sie können operator+
so ändern, dass rvalues akzeptiert werden, indem Sie rvalue-Verweise verwenden. Auf diese kann nicht an anderer Stelle im Programm verwiesen werden. Mit dieser Änderung kann operator+
jetzt eine Zeichenfolge an eine andere anfügen. Die Änderung verringert beträchtlich die Anzahl dynamischer Speicherbelegungen, die die string
-Klasse ausführen muss. Weitere Informationen zur string
-Klasse finden Sie unter basic_string
-Klasse.
Die Move-Semantik ist auch hilfreich, wenn der Compiler keine Optimierung von Rückgabewerten (Return Value Optimization, RVO) oder keine Optimierung von benannten Rückgabewerten (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 Arbeitsspeicher für seine Elemente neu zuteilen und dann jedes Element an einen anderen Arbeitsspeicherort kopieren, um für das eingefügte Element Platz zu schaffen. Wenn ein Einfügevorgang ein Element kopiert, wird zunächst ein neues Element erstellt. Dann wird der Kopierkonstruktor aufgerufen, um die Daten aus dem vorherigen Element in das neue Element zu kopieren. Abschließend wird das vorherige Element zerstört. Mithilfe der Move-Semantik können Sie Objekte direkt verschieben, ohne speicherintensive Speicherbelegungs- und Kopiervorgänge ausführen 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 Move-Semantik 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 akzeptiert. Wenn sie diese Parameter an eine andere Funktion übergibt (oder weiterleitet) – also beispielsweise, wenn sie 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&
akzeptiert, kann die Funktion nicht mithilfe eines rvalue (z. B. eines temporären Objekts oder Ganzzahlliterals) aufgerufen werden.
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. Diese Funktion kann die Argumente dann so an eine andere Funktion weiterleiten 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
-lvalue-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 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 allerdings keinen gültigen Aufruf der factory
-Funktion. Das liegt daran, dass factory
änderbare lvalue-Verweise als Parameter akzeptiert, aber unter Verwendung von rvalues aufgerufen wird:
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, damit ein lvalue-Verweis und ein rvalue-Verweis akzeptiert werden.
Wenn Sie eine Funktion überladen, um einen const
-lvalue-Verweis oder einen rvalue-Verweis zu akzeptieren, können Sie Code schreiben, der zwischen nicht änderbaren Objekten (lvalues) und änderbaren temporären Werten (rvalues) unterscheidet. Sie können ein Objekt an eine Funktion übergeben, die einen rvalue-Verweis akzeptiert, es sei denn, das Objekt ist als const
gekennzeichnet. 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 im Programm verwiesen werden kann, stellt der Aufruf eine Bindung an die überladene Version von f
her, die einen rvalue-Verweis akzeptiert, der das Objekt ändern kann.
Der Compiler behandelt einen benannten rvalue-Verweis als lvalue und einen unbenannten rvalue-Verweis als rvalue.
Funktionen, die einen rvalue-Verweis als Parameter akzeptieren, behandeln den Parameter als lvalue im Textkörper der Funktion. Der Compiler behandelt einen benannten rvalue-Verweis als lvalue. Das liegt daran, dass auf ein benanntes Objekt von mehreren Komponenten eines Programms verwiesen werden kann. Es ist gefährlich, mehreren Komponenten eines Programms das Ändern oder Entfernen von Ressourcen aus diesem Objekt zu ermöglichen. Wenn beispielsweise mehrere Komponenten eines Programms versuchen, Ressourcen aus dem gleichen 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&&).
In dem 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 einen lvalue-Verweis in einen rvalue-Verweis umwandeln.
Die std::move
-Funktion der C++-Standardbibliothek ermöglicht es Ihnen, ein Objekt in einen rvalue-Verweis auf dieses Objekt zu konvertieren. Sie können auch das Schlüsselwort static_cast
verwenden, um einen lvalue in einen rvalue-Verweis umzuwandeln, 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 Verweisreduzierungsregeln.
Eine Funktionsvorlage, die ihre Parameter an eine andere Funktion übergibt (oder weiterleitet), ist ein gängiges Muster. Es ist wichtig zu wissen, wie die Ableitung des Vorlagentyps für Funktionsvorlagen, die rvalue-Verweise akzeptieren, funktioniert.
Wenn das Funktionsargument ein rvalue ist, geht der Compiler davon aus, dass das Argument ein rvalue-Verweis ist. Stellen Sie sich beispielsweise vor, Sie übergeben einen rvalue-Verweis auf ein Objekt vom Typ X
an eine Funktionsvorlage, die den Typ T&&
als Parameter akzeptiert. Die Vorlagenargumentableitung geht davon aus, dass es sich bei T
um X
handelt. Somit hat der Parameter den Typ X&&
. Wenn das Funktionsargument ein lvalue oder const
-lvalue ist, geht der Compiler davon aus, dass der Typ ein lvalue-Verweis oder ein const
-lvalue-Verweis dieses Typs ist.
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 die einzelnen Aufrufe der print_type_and_value
-Funktion aufzulösen, führt der Compiler zunächst eine Ableitung der Vorlagenargumente aus. Anschließend verwendet der Compiler die Verweisreduzierungsregeln, wenn er die Parametertypen durch die abgeleiteten Vorlagenargumente ersetzt. 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 Verweisreduzierungsregeln, 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. Im Abschnitt Perfekte Weiterleitung wird die perfekte Weiterleitung ausführlicher beschrieben.
Zusammenfassung
Rvalue-Verweise unterscheiden lvalues von rvalues. Sie können dazu beitragen, unnötige Speicherbelegungs- und Kopiervorgänge zu beseitigen, um die Leistung Ihrer Anwendungen zu verbessern. Außerdem ermöglichen sie das Schreiben einer Funktion, die beliebige Argumente akzeptiert. Diese Funktion kann die Argumente so an eine andere Funktion weiterleiten als wäre die andere Funktion direkt aufgerufen worden.
Weitere Informationen
Ausdrücke mit unären Operatoren
lvalue-Verweisdeklarator: &
lvalues und rvalues
Bewegungskonstruktoren und Bewegungszuweisungsoperatoren (C++)
C++-Standardbibliothek