ProgrammazioneSviluppatore C++

Che cos'è 'lvalue' e 'rvalue' in C++? Spiega le loro differenze, i modi in cui vengono passati alle funzioni e perché sono state introdotte le reference agli rvalue (rvalue references)?

Supera i colloqui con l'assistente IA Hintsage

Risposta

In C++ lvalue (left value) è un'espressione che si riferisce a un oggetto in memoria, che ha un nome e a cui si può fare riferimento (ad esempio, una variabile). rvalue (right value) è un valore temporaneo, privo di nome, che non è un oggetto nel senso tradizionale (ad esempio, il risultato di a + b, literali come 5).

Si può prendere l'indirizzo di un lvalue, ma non di un rvalue (fino all'introduzione delle rvalue references). Per passare questi valori alle funzioni ci sono:

  • Riferimenti normali: void foo(const std::string& s); — accettano lvalue e rvalue.
  • Riferimenti a lvalue: void bar(std::string& s); — accettano solo lvalue.
  • Riferimenti a rvalue (C++11+): void baz(std::string&& s); — accettano solo rvalue.

Esempio:

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

Le rvalue references sono necessarie per il passaggio efficiente di oggetti temporanei, principalmente per la move semantics, per spostare risorse invece di copiarle.

Domanda trabocchetto

Quale tipo di riferimento (lvalue o rvalue) riceverà l'espressione std::move(obj)? Quale categoria diventerà l'oggetto stesso dopo l'applicazione di std::move?

Risposta:

std::move(obj) restituisce sempre un riferimento a rvalue (T&&), ma l'oggetto stesso rimane un lvalue, è semplicemente applicata una conversione esplicita. Dopo questo, si deve trattare l'oggetto con grande cautela (può essere in uno stato non definito, ma valido).

Esempio:

std::string s = "data"; std::string d = std::move(s); // d riceve i dati da s, s ora è vuota

Esempi di errori reali a causa della mancanza di conoscenza delle sottigliezze dell'argomento.


Storia

In un grande progetto, uno degli sviluppatori passava oggetti temporanei tramite riferimento a lvalue T& (invece di T&& o const T&). Questo portava a errori di compilazione e copie inefficaci — la move semantics non veniva usata, la performance diminuiva del 40%.


Storia

Nel motore frontend, veniva applicato in modo errato std::move a variabili che venivano dopo utilizzate nuovamente. Di conseguenza, le variabili erano in uno stato "danneggiato", si verificavano crash e si interrompevano i thread di rendering.


Storia

Nella libreria di serializzazione veniva passato un contenitore di tipo std::vector<T> a una funzione come lvalue, mentre si aspettavano move. Invece di spostare, si effettuava una costosa copia di un gran numero di elementi, il che peggiorava drasticamente i tempi di serializzazione su grandi array.