Prosta odpowiedź brzmi: powinieneś pisać kod dla wartości referencyjnych, tak jak normalny kod referencyjny, i powinieneś traktować je tak samo mentalnie przez 99% czasu. Obejmuje to wszystkie stare reguły dotyczące zwracania referencji (tj. Nigdy nie zwracaj referencji do zmiennej lokalnej).
O ile nie piszesz szablonowej klasy kontenera, która musi skorzystać ze std :: forward i być w stanie napisać ogólną funkcję, która przyjmuje odwołania do wartości lub wartości, jest to mniej więcej prawda.
Jedną z dużych zalet konstruktora przenoszenia i przypisania przeniesienia jest to, że jeśli je zdefiniujesz, kompilator może ich użyć w przypadkach, gdy RVO (optymalizacja wartości zwracanej) i NRVO (optymalizacja nazwanej wartości zwrotnej) nie zostaną wywołane. Jest to dość duże, jeśli chodzi o wydajne zwracanie drogich obiektów, takich jak kontenery i łańcuchy, pod względem wartości z metod.
Teraz, gdy sprawy stają się interesujące w odniesieniu do odwołań do wartości, można również użyć ich jako argumentów normalnych funkcji. Pozwala to na pisanie kontenerów, które mają przeciążenia zarówno dla stałej referencji (const foo i inne), jak i wartości referencyjnej (foo i& inne). Nawet jeśli argument jest zbyt niewygodny, aby przekazać go zwykłym wywołaniem konstruktora, nadal można to zrobić:
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));
}
Kontenery STL zostały zaktualizowane w taki sposób, że mają przeciążenia związane z przenoszeniem prawie wszystkiego (klucz skrótu i wartości, wstawianie wektorów itp.) I tam, gdzie będzie ich najwięcej.
Możesz także użyć ich do normalnych funkcji, a jeśli podasz tylko argument odwołania do wartości, możesz zmusić program wywołujący do utworzenia obiektu i pozwolić funkcji wykonać ruch. Jest to raczej przykład niż naprawdę dobre zastosowanie, ale w mojej bibliotece renderowania przypisałem ciąg do wszystkich załadowanych zasobów, aby łatwiej było zobaczyć, co każdy obiekt reprezentuje w debuggerze. Interfejs wygląda mniej więcej tak:
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;
}
Jest to forma „nieszczelnej abstrakcji”, ale pozwala mi skorzystać z faktu, że musiałem tworzyć ciąg już przez większość czasu i uniknąć tworzenia kolejnego kopiowania. Nie jest to dokładnie kod o wysokiej wydajności, ale jest dobrym przykładem możliwości, gdy ludzie znają tę funkcję. Ten kod faktycznie wymaga, aby zmienna była tymczasowa dla wywołania lub wywołana std :: move:
// move from temporary
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, string("Checkerboard"));
lub
// 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));
lub
// 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));
ale to się nie skompiluje!
string str("Checkerboard");
TextureHandle htex = CreateTexture(128, 128, A8R8G8B8, str);