R-Wert-Referenzen (C++0x)

Aus Appmethod Topics
Wechseln zu: Navigation, Suche

Nach oben zu C++0x-Features - Index


Mit BCC32 können R-Wert-Referenzen verwendet werden, die das Erstellen einer Referenz auf temporäre Werte ermöglichen. R-Wert-Referenzen verhindern unnötiges Kopieren und ermöglichen perfekte Forward-Funktionen. Dieses Leistungsmerkmal gehört zu den C++0x-Features.

Beschreibung

R-Wert-Referenzen sind zusammengesetzte Typen, wie C++-Standardreferenzen, auf die als L-Wert-Referenzen verwiesen wird. Eine L-Wert-Referenz wird gebildet, indem das Ampersandzeichen (&) an einen Typ angehängt wird:

SomeClass l;
SomeClass& lReference = l;    //lvalue reference

Eine R-Wert-Referenz wird gebildet, indem zwei Ampersandzeichen (&&) an einen Typ angehängt werden:

SomeClass r;
SomeClass&& rReference = r;    //rvalue reference

Eine R-Wert-Referenz verhält sich wie eine L-Wert-Referenz, eine R-Wert-Referenz kann jedoch an einen temporären Wert – einen R-Wert – gebunden werden.

SomeClass a;
a = SomeClass();
SomeClass& lReference = a;	        //OK-lvalue reference can bind to an lvalue such as "a"
SomeClass& lReference2 = SomeClass();	//error-lvalue reference can't bind to an rvalue
SomeClass&& rReference = SomeClass();	//OK for rvalue reference to bind to rvalue

// Both references can be used the same way
SomeOtherClass value1 = SomeFunc(lReference);
SomeOtherClass value2 = SomeFunc(rReference);

Im obigen Beispiel ist SomeClass() nicht an einen Bezeichner gebunden, daher ist es ein R-Wert und kann an eine R-Wert-Referenz gebunden werden – aber nicht an eine L-Wert-Referenz.

R-Wert-Referenzen können nicht an benannte L-Werte gebunden werden

In der C++-Spezifikation wurden neue Regeln für R-Wert-Referenzen festgelegt. Die Bindungsregeln für R-Wert-Referenzen wurden hinsichtlich eines Aspekts geändert. Eine R-Wert-Referenz kann nicht mehr an einen benannten L-Wert gebunden werden (außer bei Überladungen, die nicht geändert wurden).

Die Dinkumware-Header folgen noch den alten Regeln, daher müssen Sie mit der neuen Option -Vbv compilieren, wenn Sie R-Wert-Referenzfunktionen mit Dinkumware-Headern einsetzen möchten.

Vermeiden von unnötigem Kopieren

In vielen Fällen werden Daten kopiert, die einfach verschoben werden müssten, das heißt, die Daten müssen nicht beim Originaldatenhalter verbleiben. Ein Beispiel dafür ist das Verschieben von Daten zwischen zwei Strukturen, sodass keine Struktur ihre vorherigen Daten behält. Logisch betrachtet, würde ein einfaches Austauschen der Referenzen auf die Daten ausreichen.

R-Wert-Referenzen können zur Unterscheidung von Fällen verwendet werden, die entweder ein Kopieren oder nur das bloße Verschieben von Daten erforderlich machen. Da Kopieren ein langwieriger Vorgang sein kann, sollten Sie es soweit wie möglich vermeiden.

Wenn eine Funktion etwas kopieren muss, das als R-Wert an sie übergeben wurde, dann kann die Funktion anstatt des Kopierens ein Verschieben vornehmen, weil bekannt ist, dass der Wert temporär ist. Wird an eine Funktion ein L-Wert übergeben, muss wahrscheinlich kopiert werden. Diese Fälle lassen sich anhand der Funktionssignatur unterscheiden.

Angenommen, die Klasse "ExampleClass" hat die Funktion "clone", die ein umfangreiches Kopieren von Klasseninstanzen durchführt. Sie möchten eine "move"-Funktion definieren, die den Wert eines Objekts verschiebt. Diese Funktion könnte folgendermaßen überladen werden:

// Parameter is lvalue
ExampleClass move(ExampleClass& l)
{
    return l.clone();	//returns a copy since we can't touch the original value

// Parameter is rvalue
ExampleClass move(ExampleClass&& r)
{
    return r;		//returns a reference since we don't care about the temporary's value
}
template class MyClass<int>;
...
extern template class MyClass<int>;    // not allowed
extern template class MyClass<float>;  // OK

Anschließend kann die Funktion "move" sowohl für R-Werte als auch für L-Werte verwendet werden:

ExampleClass a, b, c;
a = ExampleClass();
b = b.move(a);			//parameter is lvalue
c = c.move(ExampleClass());	//parameter is rvalue

Beachten Sie bitte, dass die "move"-Funktion für den R-Wert-Parameter sehr wenig ausführt, daher wird die Funktion viel schneller ausgeführt als die "move"-Funktion für den L-Wert-Parameter.

Eine ähnliche Technik können Sie für Funktionen anwenden, die Kopien machen müssen, z. B. Kopierkonstruktoren und Zuweisungsoperatoren. Angenommen, es gibt eine Template-Klasse mit einem Zeiger auf eine andere Klasse, wobei "clone" wieder eine umfangreiche Kopierfunktion ist:

template <class T>
class PointerClass
{
private:
    T* pointer;
public:
    // Regular constructor
    PointerClass(void);

    // Copy constructor for lvalues
    PointerClass(PointerClass& pcl) : pointer(pcl.pointer ? pcl.pointer.clone() : 0) {}	//make full copy
    // Copy constructor for rvalues
    PointerClass(PointerClass&& pcr) : pointer(pcr.pointer) {pcr.pointer = 0;}

Der Kopierkonstruktor, der einen R-Wert übernimmt:

  • Führt ein Verschieben, kein Kopieren aus. Das heißt, er gibt einfach eine Referenz auf die Daten zurück.
  • Behandelt im Code das R-Wert-Argument pcr genauso wie einen L-Wert.
  • Belässt das R-Wertobjekt in einem definierten Zustand, sodass es sicher gelöscht werden kann.

Nicht kopierbare aber verschiebbare Typen

Typen, die nicht kopiert werden können, wie diejenigen, die unique_ptr verwenden, lassen sich aber verschieben. Obwohl die Definition von Zuweisungsoperatoren nicht möglich ist, können diese Typen Verschiebe- und Austausch-Funktionen implementieren. Solange R-Wert-Referenzen verwendet werden, sind dafür keine Kopiervorgänge erforderlich. Es könnte eine Sortier-Funktion entwickelt werden, da dafür nur ein Austauschen, aber kein Kopieren nötig ist.

Betrachten Sie zum Beispiel die folgende factory-Funktion, die ein Argument übernimmt:

template <class T, class U>
factory(const U& u)
{
    return new T(u);
}

Die obige Definition der factory-Funktion funktioniert in diesem Fall:

T* p = factory<T>(7);

Wird dagegen ein T verwendet, dessen Konstruktorparameter keine const-Referenz ist, tritt ein Compiler-Fehler auf. Sie können dies beheben, indem Sie "const" aus der Definition entfernen:

template <class T, class U>
factory(U& u)
{
    return new T(u);
}

Jetzt schlägt aber das vorherige Beispiel fehl:

T* p = factory<T>(7); // compiler error
T* u = new T(7);	//OK

Der Fehler wird verursacht, weil der Wert 7 bewirkt, dass das Template-Argument mit "int &" verglichen wird, was aber nicht an den R-Wert 7 gebunden werden kann.

Sie könnten dies durch die Definition einer factory-Funktion für jedes Vorkommen von "const" bzw. "nicht-const" beheben. Das ist jedoch problematisch, weil die Anzahl der erforderlichen Funktionen exponentiell mit der Anzahl der Argumente anwachsen würde.

Wenn Sie das Argument in eine R-Wert-Referenz umwandeln, können Sie die Situation vereinfachen:

template <class T, class U>
factory(u&& u)
{
   return new T(forward<U>(u));
}

Jetzt ist das Argument u sowohl an R- als auch an L-Werte gebunden. Die forward-Funktion gibt einen R- oder L-Wert, genauso wie er übergeben wurde, zurück. Hier die entsprechende Definition:

template <class U>
struct identity
{
    typedef U type;
};

template <class U>
U&& forward(typename identity<U>::type&& u)
{
    return u;
}

Siehe auch