programowanieProgramista C++

Co to jest 'lvalue' i 'rvalue' w C++? Wyjaśnij ich różnice, sposoby przekazywania do funkcji i po co wprowadzono referencje do rvalue (rvalue references)?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

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ą:

  • Zwykłe referencje: void foo(const std::string& s); — akceptują lvalue oraz rvalue.
  • Referencje lvalue: void bar(std::string& s); — akceptują tylko lvalue.
  • Referencje rvalue (C++11+): 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ć.

Pytanie podchwytliwe

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

Przykłady rzeczywistych błędów z powodu nieznajomości subtelności tematu.


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.