La respuesta simple es que debe escribir código para las referencias de valor como lo haría con el código de referencias regular, y debería tratarlas mentalmente el 99% de las veces. Esto incluye todas las reglas antiguas sobre la devolución de referencias (es decir, nunca devuelva una referencia a una variable local).
A menos que esté escribiendo una clase de contenedor de plantilla que necesite aprovechar std :: forward y poder escribir una función genérica que tome referencias lvalue o rvalue, esto es más o menos cierto.
Una de las grandes ventajas para el constructor de movimientos y la asignación de movimientos es que si los define, el compilador puede usarlos en los casos en que RVO (optimización del valor de retorno) y NRVO (optimización del valor de retorno) no se invoquen. Esto es bastante grande para devolver objetos caros como contenedores y cadenas por valor eficiente de los métodos.
Ahora, cuando las cosas se ponen interesantes con las referencias de valor, es que también puedes usarlas como argumentos para las funciones normales. Esto le permite escribir contenedores que tienen sobrecargas tanto para la referencia constante (const foo y otros) como para la referencia de valor (foo y otros). Incluso si el argumento es demasiado difícil de manejar con una simple llamada de constructor, todavía se puede hacer:
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));
}
Los contenedores STL se han actualizado para tener sobrecargas de movimiento para casi cualquier cosa (clave hash y valores, inserción de vectores, etc.), y es donde más los verá.
También puede usarlos para funciones normales, y si solo proporciona un argumento de referencia de valor de valor, puede obligar a la persona que llama a crear el objeto y dejar que la función haga el movimiento. Este es más un ejemplo que un uso realmente bueno, pero en mi biblioteca de representación, he asignado una cadena a todos los recursos cargados, para que sea más fácil ver qué representa cada objeto en el depurador. La interfaz es algo como esto:
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 una forma de 'abstracción permeable', pero me permite aprovechar el hecho de que ya tenía que crear la cadena la mayor parte del tiempo y evitar hacer otra copia de ella. Este no es exactamente un código de alto rendimiento, pero es un buen ejemplo de las posibilidades a medida que las personas se acostumbran a esta característica. Este código realmente requiere que la variable sea temporal a la llamada o que se invoque std :: move:
// 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));
¡pero esto no se compilará!
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, str);