[28] Sémantique par référence et par valeur

(Une partie de C++ FAQ Lite fr, Copyright © 1991-2002, Marshall Cline, cline@parashift.com)

Traduit de l'anglais par Philippe Elie

Les FAQs de la section [28]


[28.1] Qu'est-ce que la sémantique par valeur et/ou par référence, quel est la meilleure en C++?
Avec la sémantique par références, l'assignation est une copie de pointeur (i.e., une référence). La sémantique par valeurs (ou "copie") signifie que l'assignation doit copier la valeur et non simplement un pointeur. C++ donne le choix : utiliser l'opérateurd'assignation pour copier la valeur (sémantique pas valeur), ou utiliser une copie de pointeur (sémantique par référence). C++ permet de surcharger l'opérateurd' affectation pour faire ce que vous désirez, toutefois le choix par défaut (et le plus commun) est de copier la valeur.

Les avantages de la sémantique par référence: flexibilité et liaison dynamique (dynamic binding) (vous avez la liaison dynamique en C++ seulement quand vous utilisé des pointeurs ou des références pas quand vous utilisez des valeurs).

Les avatages de la sémantique par valeur: vitesse d'exécution. "vitesse" semble un curieux bénéfice pour une caractéristiques qui requiert la copie d'un objet (vs. la copie d'un un pointeur), mais le fait est qu'habituellement on accède plus souvent a un objet qu'on ne le copie, ainsi le coût de copie occasionnel est (généralement) plus que masqué par le bénéfice d'accéder directement a l'objet plutôt qu'a travers un pointeur vers l'objet.

Il y a trois cas ou nous avons un objet plutôt qu'un pointeur vers un objet: : objet local, objet global/statique, et membre objet d'une classe. Le plus important des trois est le dernier ("composition").

Plus d'informations a propos des valeurs contre références sont donnés dans les FAQ suivantes. S'il-vous-plait lisez-les toutes sinon vous n'aurez pas une perspective juste. Les premières FAQ sont intentionnellement biaisé en faveur de l'utilisation de la sémantique par valeur, aussi si vous lisez seulement les premières FAQ de cette section votre perspective du problème sera déformée.

L'assignation possède aussi d'autre subtilités (e.g., copie superficielle contre copie en profondeur) qui ne sont pas couverte ici.

[ Haut | Bas | Rechercher ]


[28.2] Qu'est-ce que une "donnée virtuelle," et comment puis-je / dois-je l'utiliser en C++?
Les données virtuelles permettent a une classe dérivé de changé la classe d'un membre d'une classe de base. Les données virtuelles ne sont pas a strictement parler "supportées" par le C++, toutefois elles peuvent être simulées en C++. Ce n'est pas très élégant mais cela fonctionne.

Pour simuler une donnée virtuelle en C++, la classe de base doit posséder un pointeur vers l'objet membre, et la classe dérivée doit fournir un objet alloué via new qui sera pointé par le pointeur membre de la classe de base. La classe de base pourrait être munie d'un ou plusieurs constructeur normaux qui fournissent leur propre pointeur (a nouveau via new), et le destructeur de la classe de base devrait détruire le pointeur via delete.

Par exemple, la class Pile peut avoir un objet Tableau membre (en utilisant un pointeur), et la class dérivée PileAgrandissable peut surcharger le membre de la class de base de Tableau à TableauAgrandissable. Pour que ceci fonctionne, TableauAgrandissable doit être dérivé de Tableau, ainsi Pile possède un Tableau*. Le constructeur par défaut de Pile initialise ce pointeur avec un Tableau* via un new Tableau, mais Pile doit aussi fournir une constructeur (éventuellement protégé:) qui reçoit en paramètres un Tableau* provenant d'une class dérivé. Le constructeur de TableauAgrandissable's fournira un new TableauAgrandissable a ce constructeur spécial.

Avantages:

Inconvénients: En d'autres mots, nous avons réussi a rendre notretravail plus facile en tant que programmeur de PileAgrandissable, mais tout les autres utilisateurs devront payé pour cette caractéristique . Malheureusement les surcoûts sont imposés aux utilisateurs de PileAgrandissableetau ceux de Pile.

S'il vous plaît lisez le reste de cette section. (Vous n'aurez pas une vue objective sans les autres.)

[ Haut | Bas | Rechercher ]


[28.3] Différence entre des données virtuelles et des données dynamiques?
La façon la plus simple de décrire cette différence est par analogie avec les fonctions virtuelles : Une fonction membre virtualimplique que la déclaration (signature) doit rester identique dans les classes dérivées, mais la définition (corps) peut être surchargé. La surcharge d'une fonction membre héritée est une propriété statique d'une classe dérivée; elle ne change pas dynamiquement durant la durée de vie d'une instance particulière d'une objet, ni ne peut être différente pour des objets distincts, instance d'une même classe dérivée.

Maintenant revenez en arrière et relisez la paragraphe précédent en faisant les substitutions:

Vous avez ainsi une définition d'une donnée virtuelle.

Une autre façon de considérer ceci est de distinguer les fonctions membres "par objet" des fonctions membres "dynamique". Une fonction "par objet" est une fonction membre qui est potentiellement différente pour chaque instance d'un objet et peut-être implémenté par un pointeur de fonction dans l'objet; ce pointeur doit être const, car le pointeur ne changera pas durant la durée de vie de l'objet. Une fonction membre "dynamique" est une fonction membre qui peut changer dynamiquement dans le temps; ceci peut aussi être implémenté par un pointeur de fonction mais le pointeur ne peut pas être const.

En étendant l'analogie, ceci nous donne trois concepts distincts pour les données membres:

La raison pour laquelle ces différents "type" de données membres se ressemblent est qu'ils ne sont pas "supportés" en C++. Ils sont simplement "permis", et dans chaque cas, le mécanisme utilisé pour l'implémentation est le même: un pointeur vers une classe de base (probablement abstraite). Dans un langage qui implémente ces différents "type" de données la différence est plus évidente car chaque cas utilise une syntaxe différente.

[ Haut | Bas | Rechercher ]


[28.4] Dois-je utiliser des pointeurs vers (alloué via new) pour mes objets données membres ou dois-je utiliser la "composition"?
Composition.

Vos objet membres devrait normalement être "contenu" dans l'objet composite (mais pas toujours; les objets "enveloppe" sont un bon exemple ou vous devrez utilisé des pointeur/référence; les relations N-to-1-uses-a (N objets utilisent un objet commun) nécessitent aussi un pointeur/référence).

Il y a trois raisons pour lesquelles des objets membres pleinement contenus ("composition") présente de meilleur performance que des pointeurs vers des objets membres alloués:

[ Haut | Bas | Rechercher ]


[28.5] Quels sont les coûts relatifs des 3 problèmes de performances associés avec l'allocation dynamique d'objet?
Les trois surcoûts sont énuméré dans la FAQ précédente: Ainsi les objets membres permettent des optimisations significative qui ne sont pas possible en utilisant l'approche "objets membres-par-pointeurs". Ceci est la principal raison qui fait que les langages utilisant la sémantique par référence ont des problèmes "inhérent" de performance.

Note: Lisez les trois FAQ suivantes pour obtenir un vision équilibré du phénomène!

[ Haut | Bas | Rechercher ]


[28.6] Les fonctions membres "inline virtual" peuvent-elles être réellement "inline"?
De temps en temps...

Quand un objet est utilisé via une référence ou un pointeur, un appel a une fonction virtual ne peut pas être mise en ligne, car l'appel doit être résolu dynamiquement. Raison: le compilateur ne peut pas connaître le code à appeler avant l'exécution (i.e., dynamiquement), car le code peut être fourni par un classe dérivée qui a été crée après que l'appelant ait été compilé.

Aussi la seul fois où une fonction inline virtual peut-être mise en ligne est lorsque le compilateur connaît le type "exact de la classe" de l'objet qui est la cible de l'appel la fonction virtual. Ceci ne peut arriver que si le compilateur manipule un objet plutôt qu'un pointeur ou un référence vers un objet. I.e., soit avec un objet local, un objet global/statique, ou un objet contenu dans une classe.

Notez que la différence de surcoût en vitesse entre un fonction mise en ligne et non mise en ligne est normalement beaucoup plus significatif que la différence entre un appel de fonction "normal" et un appel de fonction virtual. Par exemple, la différence entre un appel de fonction "normal" et un appel de fonction virtual est souvent de deux références mémoires, mais le différence entre une fonction en ligne et une fonction non en ligne peut aller jusqu'à un ordre de grandeur (pour des zillions d'appels a des fonctions trivial, la perte de la mise en ligne de fonction virtuel peut résulter en un dégradation de la vitesse par 25 ! [Doug Lea, "Customization in C++," proc Usenix C++ 1990]).

Une conséquence pratique de ceci: ne vous laissez pas leurrer par des débats sans fin (ou des tactiques de ventes!) de vendeurs de compilateur/langage qui comparent le coût d'un appel de fonction virtual dans leur langage/compilateur avec un autre langage/compilateur. De telles comparaisons sont largement sans significations quand il est comparé avec la capacité du langage/compilateur de mettre en ligne les appels de fonctions. I.e., beaucoup de vendeurs d'implémentation de langage font beaucoup de bruit sur comment fonctionne leurs stratégies d'appels de fonctions virtuelles, mais si ces implémentations ne mettent pas en ligne les appels de fonctions les performances globales des programmes seront pauvres car c'est la mise en ligne pas la stratégie d'appel qui a le plus d'impact sur les performances

Note: Lisez les deux FAQ suivantes pour voir l'autre facette de ce point de vue!

[ Haut | Bas | Rechercher ]


[28.7] Il me semble que je ne devrais jamais utiliser les références. Est-ce vrai?
Faux.

La sémantique par références est Une Bonne Chose. Nous ne pouvons pas vivre sans pointeurs. Nous voulons seulement que nos logiciels ne soient pas de gigantesque nid de pointeurs. En C++, vous pouvez choisir ou vous voulez utiliser la sémantique par références (pointeurs/références) et où vous voulez la sémantique par valeurs (où les objets sont physiquement contenus pas d'autres objets etc.) Dans une grand système, cela devrait être un équilibré. Toutefois si vous implémentez absolument toute chose avec des pointeurs, vous aurez un fort surcoût à l'exécution.

Les objets proches du problèmes sont plus grand que les objets de haut niveau. L' identité de ces abstractions proches du problème sont habituellement plus importante que leur "valeur" Aussi la sémantique par références devrait-étre utilisée pour ces objets.

Notez que les objets dans l'espace du problème sont normalement a un plus haut niveau que les objets dans l'espace des solutions, donc les objets dans l'espace du problème ont normalement une fréquence plus basse d'interaction . Ainsi le C++ nous donne une situation idéalle : nous choisissons la sémantique par références pour les objets qui nécessite une identité unique ou qui sont trop grand pour être copié, et nous choisissons la sémantique par valeur pour les autres. Ainsi les objets avec une fréquence élevée d'utilisation auront une sémantique par valeur, car nous choisissons la flexibilité là ou elle n'entraînera que peu de surcoût, et nous aurons les performances là ou elles sont le plus nécessaires!

Ceci sont quelques unes des directions à prendre pour faire de la vrai conception OO. La maîtrise OO/C++ prend du temps et demande un entraînement de haute qualité. Si vous voulez un outil puissant vous devrez vous investir.

Ne vous arrêtez pas! Lisez la FAQ suivante aussi!!

[ Haut | Bas | Rechercher ]


[28.8] Est-ce que les pauvres performances des références signifie que je devrait utilisé le passage de paramètre par valeur?
Non.

La FAQ précédente discutait des objets membres, pas des paramètres. Généralement, les objets qui sont une partie d'une hiérarchie de classe devrait être passé par référence ou par pointeur, pas par valeur, ainsi et seulement ainsi vous aurez le comportement (désiré) de typage dynamique (la sémantique par valeur ne fonctionne pas bien avec l'héritage, car les objets de classe parente sont "émincés (N.D.T sliced)" quand il sont passés par valeur comme une classe de base).

A moins de contraintes supplémentaires les objets membres devraient utiliser la sémantique par valeur et les paramètres la sémantique par référence. Les discussions dans les FAQ précédentes donne quelques une des "contraintes supplémentaires" pour lesquelles des objets membres devraient être inclus par référence.

[ Haut | Bas | Rechercher ]


E-mail Marshall Cline Ecrire à l'auteur, au traducteur, ou en savoir plus sur la traduction.
C++ FAQ Lite fr | Table des matières | Index | A propos de l'auteur | © | Téléchargez votre propre copie ]
Dernière révision Sun Apr 13 23:54:39 PDT 2003