ProgrammationDéveloppeur C++

Qu'est-ce que 'lvalue' et 'rvalue' en C++ ? Expliquez leurs différences, les manières de les passer à des fonctions et pourquoi les références rvalue (rvalue references) ont été introduites ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

En C++, lvalue (valeur à gauche) est une expression qui fait référence à un objet en mémoire, qui a un nom et auquel on peut accéder (par exemple, une variable). rvalue (valeur à droite) est une valeur temporaire, sans nom, qui n'est pas un objet au sens traditionnel (par exemple, le résultat de a + b, des littéraux comme 5).

On peut obtenir l'adresse d'un lvalue, mais pas d'un rvalue (avant l'introduction des références rvalue). Pour les passer à des fonctions, on a :

  • Références normales : void foo(const std::string& s); — acceptent lvalues et rvalues.
  • Références lvalue : void bar(std::string& s); — acceptent uniquement les lvalues.
  • Références rvalue (C++11+) : void baz(std::string&& s); — acceptent uniquement les rvalues.

Exemple :

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

Les références rvalue sont nécessaires pour passer efficacement des objets temporaires, principalement pour la sémantique du déplacement (move semantics), afin de déplacer des ressources plutôt que de les copier.

Question piège

Quel type de référence (lvalue ou rvalue) obtiendra l'expression std::move(obj) ? Quelle catégorie deviendra l'objet lui-même après l'application de std::move ?

Réponse :

std::move(obj) renvoie toujours une référence rvalue (T&&), mais l'objet lui-même reste une lvalue, il subit simplement une transformation explicite. Après cela, il faut faire très attention à l'objet (il peut être dans un état indéfini mais valide).

Exemple :

std::string s = "data"; std::string d = std::move(s); // d reçoit les données de s, s est maintenant vide

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet.


Histoire

Dans un grand projet, un des développeurs passait des objets temporaires à travers une référence lvalue T& (au lieu de T&& ou const T&). Cela a entraîné des erreurs de compilation et de coûteuses copies — la sémantique de déplacement n'était pas utilisée, et la performance a diminué de 40%.


Histoire

Dans le moteur front-end, std::move était mal appliqué à des variables, qui étaient ensuite réutilisées. Cela a causé des variables dans un état "détruit", des plantages et des terminaisons anormales des threads de rendu.


Histoire

Dans une bibliothèque de sérialisation, des conteneurs de type std::vector<T> étaient passés à une fonction comme lvalue, alors qu'un déplacement était attendu. Au lieu d'un déplacement, il y avait une coûteuse copie d'un grand nombre d'éléments, ce qui a considérablement dégradé le temps de sérialisation sur de grands tableaux.