Die einfache Antwort ist, dass Sie Code für rvalue-Referenzen wie normalen Referenzcode schreiben sollten, und Sie sollten sie in 99 % der Fälle mental gleich behandeln. Dies schließt alle alten Regeln über die Rückgabe von Referenzen ein (d.h. niemals eine Referenz auf eine lokale Variable zurückgeben).
Es sei denn, Sie schreiben eine Template-Containerklasse, die die Vorteile von std::forward nutzen muss und in der Lage ist, eine generische Funktion zu schreiben, die entweder lvalue- oder rvalue-Referenzen annimmt, ist dies mehr oder weniger wahr.
Einer der großen Vorteile des move-Konstruktors und der move-Zuweisung ist, dass der Compiler sie in Fällen verwenden kann, in denen die RVO (Rückgabewertoptimierung) und NRVO (benannte Rückgabewertoptimierung) nicht aufgerufen werden können, wenn Sie sie definieren. Dies ist sehr wichtig für die effiziente Rückgabe von teuren Objekten wie Containern und Strings aus Methoden.
Interessant wird es bei rvalue-Referenzen, weil man sie auch als Argumente für normale Funktionen verwenden kann. Dies ermöglicht es Ihnen, Container zu schreiben, die Überladungen sowohl für const-Referenzen (const foo& other) als auch für rvalue-Referenzen (foo&& other) haben. Selbst wenn das Argument zu unhandlich ist, um es mit einem einfachen Konstruktoraufruf zu übergeben, kann man es trotzdem tun:
std::vector vec;
for(int x=0; x<10; ++x)
{
// automatically uses rvalue reference constructor if available
// because MyCheapType is an unamed temporary variable
vec.push_back(MyCheapType(0.f));
}
std::vector vec;
for(int x=0; x<10; ++x)
{
MyExpensiveType temp(1.0, 3.0);
temp.initSomeOtherFields(malloc(5000));
// old way, passed via const reference, expensive copy
vec.push_back(temp);
// new way, passed via rvalue reference, cheap move
// just don't use temp again, not difficult in a loop like this though . . .
vec.push_back(std::move(temp));
}
Die STL-Container wurden aktualisiert, um Move-Überladungen für fast alles zu haben (Hash-Schlüssel und -Werte, Vektoreinfügung usw.), und hier werden Sie sie am häufigsten sehen.
Man kann sie auch für normale Funktionen verwenden, und wenn man nur ein rvalue-Referenzargument angibt, kann man den Aufrufer dazu zwingen, das Objekt zu erstellen und die Funktion die Verschiebung vornehmen zu lassen. Dies ist eher ein Beispiel als eine wirklich gute Anwendung, aber in meiner Rendering-Bibliothek habe ich allen geladenen Ressourcen eine Zeichenkette zugewiesen, so dass es einfacher ist, im Debugger zu sehen, was jedes Objekt darstellt. Die Schnittstelle sieht in etwa so aus:
TextureHandle CreateTexture(int width, int height, ETextureFormat fmt, string&& friendlyName)
{
std::unique_ptr<TextureObject> tex = D3DCreateTexture(width, height, fmt);
tex->friendlyName = std::move(friendlyName);
return tex;
}
Es handelt sich um eine Form der "undichten Abstraktion", die es mir jedoch ermöglicht, die Tatsache zu nutzen, dass ich die Zeichenfolge in den meisten Fällen bereits erstellen musste, und zu vermeiden, dass sie noch einmal kopiert wird. Dies ist nicht gerade ein hochleistungsfähiger Code, aber ein gutes Beispiel für die Möglichkeiten, die sich ergeben, wenn man sich mit dieser Funktion vertraut gemacht hat. Dieser Code erfordert eigentlich, dass die Variable entweder ein temporäres Element für den Aufruf ist oder std::move aufgerufen wird:
// move from temporary
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string("Checkerboard"));
o
// explicit move (not going to use the variable 'str' after the create call)
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, std::move(str));
o
// explicitly make a copy and pass the temporary of the copy down
// since we need to use str again for some reason
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string(str));
aber das lässt sich nicht kompilieren!
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, str);