Blog

C++11 : Les RValues References

jeremy_chanut
Share Button

C++11 est une norme du langage C++ parue en 2011. Cette dernière apporte énormément de nouveautés au sein du langage. Nous nous intéresserons ici aux “RValue References”.

RValue References ?

Tout d’abord nous allons définir ce qu’est exactement une rvalue reference, mais tout d’abord, petit rappel.

Si vous avez déjà programmé vous savez tous (sans forcement le savoir) ce qu’est une lvalue (left value). Une lvalue est une valeur et une expression dont on peut récupérer l’adresse, on peut lui affecter des valeurs. Voici des exemples :

int i;
i = 1; // Ici i est une lvalue

 

int i = 42;
int& getI()
{
    return i;
}

getI() = 2; // La valeur retourner par getI() est une lvalue

 

Cependant, imaginez la fonction suivante :

int i = 42;
int getI()
{
    return i;
}

getI() = 2; // Ce coup ci le compilateur vous retournera une erreur

 

Pourquoi cela n’a pas marché ? Tout simplement parce que, ce que nous retourne getI() n’est plus une lvalue, il s’agit d’un objet temporaire créé par le compilateur, destiné à être détruit. Le standard du C++ précise que les objets temporaires ne peuvent être reçus que dans des références constantes. Le code suivant n’est donc pas valide :

int& j = getI();

 

En revanche le code suivant l’est :

int&& j = getI();

 

Ceci, mes amis, est un nouvel élément de syntaxe apporté par le C++11. Avoir && au lieu de & permet de préciser que l’on attend une RValue reference et non pas une LValue reference.

Pour résumer, les RValue references nous permettent de récupérer des références sur des objets temporaires, des rvalues.

Je cherche un job dans lequel je me sens bien maintenant !

Quelle utilité ?

Jusque là, beaucoup de nouvelles choses qui peuvent sembler inutilement compliquées, c’est normal. Sachez qu’une grande partie de la puissance RR (RValue Reference) arrive quand on les passe en arguments à des fonctions. Exemples :

void print(const std::string& s)
{
        std::cout << s;
}
 
void print(const std::string&& s)
{
        std::cout << s;
}

string getName ()
{
    return "Alex";
}

std::string toto = "alex";
print(me);        // Appelle la première fonction
print(getName()); // Appelle la deuxième fonction

 

Qu’avons-nous là ? Un moyen de détecter (et donc de différencier) les arguments de types temporaires ou non.

Fini les copies inutiles

Prenez le code suivant :

std::vector getData() 
{
    std::vector data;
    // On remplit le vector...

    return data;
}

int main() 
{
    std::vector data = retrieveDatas();

    return 0;
}

 

Ce code peut sembler inoffensif mais il est, en réalité, très inefficace :

Dans la fonction, on crée le vector contenant plein de données potentiellement très volumineuses.
Lors du return, ce vector sera copié dans un objet temporaire.
Le vector temporaire sera ensuite copié dans le vector data.
L’objet temporaire est détruit.

Au final, nous avons 2 copies et 1 destruction inutiles et potentiellement très coûteuses. Tout ce que nous voulions faire, était de déplacer le contenu du vector data créé dans getData() dans notre vector data.

Evidemment, il est possible d’éviter cela par plusieurs méthodes, comme créer le vector à l’avance et retourner une référence sur celui-ci. Mais cela complique la tâche et la rend inutilement compliquée au programmeur, qui lui, cherche une solution simple et efficace.

Voici une version utilisant les RR :

std::vector getData() 
{
    std::vector data;
    // On remplit le vector...

    return data;
}

int main() 
{
    std::vector&& data = retrieveDatas();

    return 0;
}

 

De cette manière, on a pu récupérer le contenu du vector data créé dans la fonction via l’objet temporaire. Par conséquent, plus de copies inutiles et l’objet temporaire ne sera pas détruit immédiatement. De plus, le code reste parfaitement lisible et simple.

Je cherche un job dans lequel je me sens bien maintenant !

Conclusion

Il ne s’agit que d’une introduction à la puissance des RR, elles permettent de faire tellement de choses qu’il m’est impossible de tout lister dans un seul article. Je n’ai, ici, qu’effleuré ce concept.

Toutefois si le sujet vous intéresse et que vous souhaitez en savoir plus, je vous conseille de rechercher sur internet les sujets suivants :

RValue References
Move Semantics
Perfect Forwarding

Jerémy

Envie de tester votre niveau technique en #C++ ?

Vous souhaitez devenir blogueur pour JobProd ?

Share Button

Un commentaire

  • Aurelien dit :

    Présenter ce sujet n’est pas simple car c’est certainement l’aspect le plus complexe de C++11.

    Pour information, les compilateurs supportent depuis longtemps déjà la Named Return Value Optimization qui permet de ne pas créer inutilement un temporaire dans l’exemple « non optimal » donné, même en C++98.

    De plus, en C++11, grave à la move semantic, le contenu du vecteur ne sera pas recopié, mais transféré depuis le temporaire vers sa nouvelle destination, et c’est précisément ce à quoi servent les RR : rendre transparent pour l’utilisateur l’élimination des temporaires. C’est ce qui permet de rendre un code C++98 plus performant simplement en le recompilant avec un compilo C++11.

    Ainsi je ne pense pas qu’utiliser de cette manière les RR (déclaration explicite) soit quelque chose à recommander car ça complexifie l’écriture du code au quotidien sans vraiment apporter grand chose en terme de performances.

    Mais il faut bien commencer quelque part pour introduire ce concept donc merci pour l’article :)

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>