W C++ lvalue (left value) to wyrażenie, które odnosi się do obiektu w pamięci, który ma nazwę i do którego można się odwoływać (np. zmienna). rvalue (right value) to wartość tymczasowa, która nie ma nazwy i nie jest obiektem w tradycyjnym sensie (np. wynik a + b, literały typu 5).
Lvalue można wziąć pod adres, a rvalue — nie (do czasu wprowadzenia referencji rvalue). Aby przekazywać je do funkcji, istnieją:
void foo(const std::string& s); — akceptują lvalue oraz rvalue.void bar(std::string& s); — akceptują tylko lvalue.void baz(std::string&& s); — akceptują tylko rvalue.Przykład:
void takeValue(std::string& s) { } // lvalue void takeRValue(std::string&& s) { } // rvalue std::string s = "hello"; takeValue(s); // OK, lvalue takeRValue(std::string("hi")); // OK, rvalue
Referencje rvalue są potrzebne do efektywnego przekazywania obiektów tymczasowych, głównie dla semantyki przenoszenia, aby przenosić zasoby, a nie je kopiować.
Jaki typ referencji (lvalue lub rvalue) otrzyma wyrażenie
std::move(obj)? Jakiej kategorii stanie się sam obiekt po zastosowaniu std::move?
Odpowiedź:
std::move(obj) zawsze zwraca referencję rvalue (T&&), ale sam obiekt pozostaje lvalue, po prostu jest poddawany jawnemu przekształceniu. Po tym należy bardzo ostrożnie obchodzić się z obiektem (może on być w nieokreślonym, ale ważnym stanie).
Przykład:
std::string s = "data"; std::string d = std::move(s); // d otrzymuje dane s, s jest teraz pusty
Historia
W dużym projekcie jeden z programistów przekazywał obiekty tymczasowe przez referencję lvalue T& (zamiast T&& lub const T&). Prowadziło to do błędów kompilacji i nieoptymalnych kopiowań — semantyka przenoszenia nie była używana, a wydajność spadła o 40%.
Historia
W silniku frontendowym błędnie stosowano std::move do zmiennych, które następnie były używane ponownie. W wyniku tego zmienne znajdowały się w "zepsutym" stanie, powodując awaryjne zakończenia i zawieszanie się wątków renderujących.
Historia
W bibliotece serializacji przekazywano kontener typu std::vector<T> do funkcji jako lvalue, podczas gdy oczekiwano move. Zamiast przenoszenia zachodziło kosztowne kopiowanie dużej liczby elementów, co drastycznie pogorszyło czas serializacji na dużych tablicach.