A resposta simples é que você deve escrever um código para as referências rvalue, como faria com o código de referência regular, e tratá-las mentalmente 99% do tempo. Isso inclui todas as regras antigas sobre o retorno de referências (ou seja, nunca retorne uma referência a uma variável local).
A menos que você esteja escrevendo uma classe de contêiner de modelo que precise tirar proveito do std :: forward e possa escrever uma função genérica que use referências lvalue ou rvalue, isso é mais ou menos verdadeiro.
Uma das grandes vantagens para o construtor de movimento e a atribuição de movimento é que, se você os definir, o compilador poderá usá-los nos casos em que o RVO (otimização do valor de retorno) e o NRVO (otimização do valor de retorno) não serão chamados. Isso é muito grande para devolver objetos caros, como contêineres e strings, pelo valor de forma eficiente dos métodos.
Agora, onde as coisas ficam interessantes com as referências rvalue, é que você também pode usá-las como argumentos para funções normais. Isso permite que você escreva contêineres com sobrecargas para referência const (const foo & other) e rvalue reference (foo && other). Mesmo que o argumento seja muito difícil de passar com uma simples chamada de construtor, ele ainda pode ser feito:
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));
}
Os contêineres STL foram atualizados para sobrecargas de movimentação para quase qualquer coisa (chave e valores de hash, inserção de vetor etc.) e é onde você os vê mais.
Você também pode usá-los para funções normais e, se você fornecer apenas um argumento de referência rvalue, poderá forçar o chamador a criar o objeto e permitir que a função faça a movimentação. Isso é mais um exemplo do que um uso realmente bom, mas, na minha biblioteca de renderização, atribui uma string a todos os recursos carregados, para que seja mais fácil ver o que cada objeto representa no depurador. A interface é algo como isto:
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;
}
É uma forma de "abstração com vazamento", mas me permite aproveitar o fato de que eu já tinha criado a string na maior parte do tempo e evitar fazer mais uma cópia dela. Este não é exatamente um código de alto desempenho, mas é um bom exemplo das possibilidades de as pessoas entenderem esse recurso. Esse código realmente exige que a variável seja temporária para a chamada ou std :: move invocada:
// move from temporary
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string("Checkerboard"));
ou
// 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));
ou
// 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));
mas isso não será compilado!
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, str);