FAQ C++Consultez toutes les FAQ

Nombre d'auteurs : 35, nombre de questions : 368, dernière mise à jour : 23 mai 2017  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.


SommaireLes fonctionsOptimisation (5)
précédent sommaire suivant
 

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... 
};
Les constructeurs et destructeur contiennent une instruction d'écriture sur la sortie standard. Ainsi, nous aurons un rapport détaillé des différentes opérations effectuées.

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"; 
}
Notre code contient également une fonction build_sport_car(), renvoyant un objet de type car :

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); 
}
Enfin, la fonction main() se contente d'appeler la fonction build_sport_car() :

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; 
}
Si l'on désactive la RVO, le programme produit la sortie suivante :

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)
  1. Tout d'abord, la fonction build_sport_car(), appelée depuis main(), crée une instance de car.
  2. 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().
  3. 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.
  4. De retour à la fonction main(), c'est au tour de l'objet temporaire anonyme d'être copié dans l'objet my_car
  5. … avant d'être détruit à son tour.
  6. 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.
Comme dans le cas précédent, la fonction build_sport_car() crée l'instance de car. Mais cette fois-ci, aucun objet temporaire n'est créé et aucune copie vers l'objet my_car n'est effectuée.
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.

Mis à jour le 15 octobre 2009 Florian Goo

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; 
}

Mis à jour le 15 octobre 2009 Florian Goo

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. 
    } 
}
En revanche, un léger réaménagement du code rendra son application possible :

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::auto_ptr<engine> e; 
    std::auto_ptr<wheels> w; 
    std::auto_ptr<doors> d; 
    std::auto_ptr<steering_wheel> s; 
  
    if(sport_type) 
    { 
        e = std::auto_ptr<engine>(new engine(build_sport_engine())); 
        w = std::auto_ptr<wheels>(new wheels(build_sport_wheels())); 
        d = std::auto_ptr<doors>(new doors(build_sport_doors())); 
        s = std::auto_ptr<steering_wheel>(new steering_wheel(build_sport_steering_wheel())); 
        // fabrication des autres composants... 
    } 
    else 
    { 
        e = std::auto_ptr<engine>(new engine(build_town_engine())); 
        w = std::auto_ptr<wheels>(new wheels(build_town_wheels())); 
        d = std::auto_ptr<doors>(new doors(build_town_doors())); 
        s = std::auto_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 
}

Mis à jour le 15 octobre 2009 Florian Goo

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; 
}
Commençons par écrire cette fonction de façon académique, en prenant une référence constante :

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... 
}
La (N)RVO agissant, le programme produit la sortie suivante :

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.
Écrivons maintenant une nouvelle version de cette fonction, en passant l'instance par valeur :

Code c++ : Sélectionner tout
1
2
3
4
void copy_and_modify_car(car c) 
{ 
    // modifier c... 
}
Contre toute attente, aucune copie implicite n'est créée :

Code : Sélectionner tout
1
2
3
Appel au constructeur de car. 
Fin du programme. 
Appel au destructeur de car.
Le principe de la (N)RVO (la suppression des objets temporaires intermédiaires) est ici étendu au passage d'objet.
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; 
}

Mis à jour le 15 octobre 2009 Florian Goo

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.

Mis à jour le 15 octobre 2009 Florian Goo

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 ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les 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 © 2017 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.

 
Contacter le responsable de la rubrique C++