FAQ C++Consultez toutes les FAQ
Nombre d'auteurs : 34, nombre de questions : 368, dernière mise à jour : 14 novembre 2021 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.
Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.
Sur ce, nous vous souhaitons une bonne lecture.
La RVO (pour Return Value Optimization, optimisation de la valeur de retour) est une optimisation automatique du code source effectuée par la plupart des compilateurs C++.
Cette optimisation s'applique lors d'un appel de fonction renvoyant un objet dont la copie est couteuse. Elle consiste à modifier la façon dont la fonction renvoie son objet de retour, de telle façon à ce qu'aucune copie dudit objet ne soit créée. Il en résulte alors un code potentiellement bien plus rapide.
Prenons comme exemple le code suivant.Celui-ci définit tout d'abord une classe car (voiture), composée de nombreux objets. Cette classe comprend un constructeur, un constructeur par copie et un destructeur.
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class car { public: //constructeur car ( const engine& e, //moteur const wheels& w, //roues const doors& d, //portières const steering_wheel& s //volant // etc. ); // constructeur par copie car(const car& c); // destructeur ~car(); private: engine engine_; wheels wheels_; doors doors_; steering_wheel steering_wheel_; // d'autres composants... }; |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | car::car ( const engine& e, const wheels& w, const doors& d, const steering_wheel& s // etc. ): engine_(e), wheels_(w), doors_(d), steering_wheel_(s) // etc. { std::cout << "Appel au constructeur de car.\n"; } car::car(const car& s): engine_(s.engine_), wheels_(s.wheels_), doors_(s.doors_), steering_wheel_(s.steering_wheel_) // etc. { std::cout << "Appel au constructeur par copie de car.\n"; } car::~car() { std::cout << "Appel au destructeur de car.\n"; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 | const car build_sport_car() { const engine e = build_sport_engine(); const wheels w = build_sport_wheels(); const doors d = build_sport_doors(); const steering_wheel s = build_sport_steering_wheel(); //fabrication des autres composants... return car(e, w, d, s); } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 | int main() { car my_car = build_sport_car(); std::cout << "Fin du programme.\n"; return 0; } |
Code : | Sélectionner tout |
1 2 3 4 5 6 7 | Appel au constructeur de car. (1) Appel au constructeur par copie de car. (2) Appel au destructeur de car. (3) Appel au constructeur par copie de car. (4) Appel au destructeur de car. (5) Fin du programme. Appel au destructeur de car. (6) |
- Tout d'abord, la fonction build_sport_car(), appelée depuis main(), crée une instance de car.
- Le retour de l'objet créé se traduit par sa copie vers un objet temporaire anonyme. Cet objet temporaire sera en fait la valeur de l'expression « build_sport_car() » située dans la fonction main().
- Une fois l'objet copié avec succès, la fonction build_sport_car() se termine. Les objets de la pile créés par cette fonction, comprenant l'instance de car, sont détruits un à un.
- De retour à la fonction main(), c'est au tour de l'objet temporaire anonyme d'être copié dans l'objet my_car…
- … avant d'être détruit à son tour.
- Enfin, le programme se terminant, l'objet my_car est détruit. Il n'existe alors plus d'instance de car.
Activons maintenant la RVO (il s'agit du réglage par défaut de tous les compilateurs supportant cette optimisation).
Le programme produit une sortie tout à fait différente.
Code : | Sélectionner tout |
1 2 3 | Appel au constructeur de car. Fin du programme. Appel au destructeur de car. |
Le compilateur a détecté que l'instance anonyme de voiture créée dans la fonction build_sport_car() devait être copiée vers l'objet my_car.
Une fois l'optimisation appliquée, ces deux objets ne font plus qu'un. Nous évitons alors deux copies et deux destructions, et gagnons ainsi en vitesse d'exécution.
Un type d'optimisation similaire peut également être appliqué dans le cas où l'instance de retour de la fonction est nommée. On parle alors de NRVO (Named Return Value Optimization, pour optimisation de la valeur de retour nommée).
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | const car build_sport_car() { const engine e = build_sport_engine(); const wheels w = build_sport_wheels(); const doors d = build_sport_doors(); const steering_wheel s = build_sport_steering_wheel(); // fabrication des autres composants... car sport_car(e, w, d, s); //l'instance est nommée sport_car.price(75000); sport_car.build_date(today()); return sport_car; } |
Là où il est intéressant d'être conscient de l'existence de ces deux types d'optimisation, c'est que leur application dépend de la façon dont sont écrites les fonctions concernées.
En effet, il pourra être impossible pour le compilateur de déterminer l'instance de retour si la fonction comporte plusieurs points de sortie (plusieurs instructions return) avec des instances différentes.
Par exemple, la NRVO ne pourra être appliquée au code suivant :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | const car build_car(const bool type_sport = true) { if(type_sport) { const engine e = build_sport_engine(); const wheels w = build_sport_wheels(); const doors d = build_sport_doors(); const steering_wheel s = build_sport_steering_wheel(); // fabrication des autres composants... car sport_car(e, w, d, s); //une première instance. sport_car.price(75000); return sport_car; //un premier point de sortie. } else { const engine e = build_town_engine(); const wheels w = build_town_wheels(); const doors d = build_town_doors(); const steering_wheel s = build_town_steering_wheel(); // fabrication des autres composants... car town_car(e, w, d, s); // une autre instance. town_car.price(28000); return town_car; // un autre point de sortie. } } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | const car build_car(const bool sport_type = true) { std::unique_ptr<engine> e; std::unique_ptr<wheels> w; std::unique_ptr<doors> d; std::unique_ptr<steering_wheel> s; if(sport_type) { e = std::unique_ptr<engine>(new engine(build_sport_engine())); w = std::unique_ptr<wheels>(new wheels(build_sport_wheels())); d = std::unique_ptr<doors>(new doors(build_sport_doors())); s = std::unique_ptr<steering_wheel>(new steering_wheel(build_sport_steering_wheel())); // fabrication des autres composants... } else { e = std::unique_ptr<engine>(new engine(build_town_engine())); w = std::unique_ptr<wheels>(new wheels(build_town_wheels())); d = std::unique_ptr<doors>(new doors(build_town_doors())); s = std::unique_ptr<steering_wheel>(new steering_wheel(build_town_steering_wheel())); // fabrication des autres composants... } car new_car(*e, *w, *d, *s); // une seule instance new_car.price(sport_type ? 75000 : 28000); return new_car; // un seul point de sortie } |
Tout bon cours sur le C++ vous dira qu'il est préférable de passer les objets aux fonctions par référence constante plutôt que par valeur. Il existe cependant un cas où cette assertion est fausse : celui où la (N)RVO s'en mêle.
Soit une fonction effectuant une copie, puis une suite de modifications d'une instance de voiture. Nommons simplement cette fonction copy_and_modify_car().
La fonction copy_and_modify_car() est appelée depuis la fonction main() de la façon suivante :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 | int main() { copy_and_modify_car(build_car()); //on passe directement le retour de la fonction build_car() std::cout << "Fin du programme.\n"; return 0; } |
Code c++ : | Sélectionner tout |
1 2 3 4 5 | void copy_and_modify_car(const car& const_c) { car c(const_c); // modifier c... } |
Code : | Sélectionner tout |
1 2 3 4 5 | Appel au constructeur de car. Appel au constructeur par copie de car. Appel au destructeur de car. Fin du programme. Appel au destructeur de car. |
Code c++ : | Sélectionner tout |
1 2 3 4 | void copy_and_modify_car(car c) { // modifier c... } |
Code : | Sélectionner tout |
1 2 3 | Appel au constructeur de car. Fin du programme. Appel au destructeur de car. |
L'expression « build_car() » de la fonction main() est associée à une instance temporaire de voiture. Cet objet temporaire n'ayant de toute façon d'autre destin que d'être détruit une fois la copie implicite effectuée, le compilateur juge bon (à raison) de le passer directement à la fonction copy_and_modify_car() plutôt que d'en effectuer une copie.
Conséquence de ce raisonnement : l'utilisation d'une instance nommée (donc non-temporaire) empêche l'application de cette optimisation :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 | int main() { car c = build_car(); copy_and_modify_car(c); //pas d'optimisation liée à la (N)RVO. // étant donné que c peut toujours être utilisé dans la suite du programme return 0; } |
Il est important de noter que cette optimisation est implicite. Par conséquent, elle ne permet en aucun cas de retourner des objets d'un type défini comme non-copiable.
En effet, l'écriture d'une fonction retournant un tel objet mènerait à une erreur de compilation, même si en pratique aucune copie n'aurait été effectuée.
Heureusement, le standard du langage C++11 introduit la notion de sémantique de mouvement, permettant de passer outre cette limitation.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.