| ||
auteurs : Anomaly, Aurélien Regat-Barrel, Luc Hermitte | ||
Le mot-clé virtual permet de supplanter une fonction membre d'une classe parent depuis une classe dérivée (à condition qu'elle ait la même signature).
Dans cet exemple, F1() est redéfinie statiquement par B, c'est à dire que le compilateur utilise le type officiel de la variable pour
savoir quelle fonction appeler. Ainsi, si on appelle F1() depuis un objet de type A, ce sera toujours A::F1() qui sera appelé, et si l'objet est de type B ce sera B::F1() indépendamment du fait qu'il peut s'agir d'un pointeur ou d'une référence sur A qui désigne en réalité un objet de type B (cas d'utilisation polymorphique de B). L'appel à une fonction membre virtuelle n'est au contraire pas déterminé à la compilation mais lors de l'exécution. Le fait que A::F2() soit déclarée virtual et supplantée par B::F2() signifie qu'à chaque appel de F2() le compilateur va tester le type réel de l'objet afin d'appeler B::F2() s'il le peut. Sinon il appellera A::F2(). On parle alors de liaison dynamique (dynamic binding en anglais) par opposition à la liaison statique faite lors de l'édition de liens. La virtualité implique l'utilisation de pointeurs ou de références. Ceci est illustré par le 3° exemple du code ci-dessus qui effectue une recopie non polymorphique d'un objet B vers un objet A. Dans ce cas l'objet B est "tronqué" (pour éviter ce problème il faut passer par une copie polymorphique, voir Comment effectuer la copie d'objets polymorphiques ?) et on obtient un objet de type A malgré que l'on soit parti d'un objet de type B. Ce n'est pas le cas avec l'utilisation de pointeurs ou références, qui bien que déclarés comme étant des pointeurs / références sur des objets de types A peuvent désigner des objets de type B comme dans les deux derniers exemples du code précédent. Le type statique de pa est A* mais son type dynamique est B*. De même, le type dynamique de ra est B, ce qui explique que pa->F2() et ra.F2() provoquent l'appel de B::F2() alors que statiquement c'est A::F2() qui aurait du être appelé. Notez que cet exemple n'inclut pas de destructeur virtuel par souci de simplification, mais ceci serait nécessaire. Pour plus d'explications, lire la question Pourquoi et quand faut-il créer un destructeur virtuel ?. |
| ||
auteur : Marshall Cline | ||
L'appel dynamique permet d'augmenter la réutilisabilité en autorisant le 'vieux' code à appeler du nouveau code.
Avant l'apparition de l'orientation objet, la réutilisation du code se faisait en appelant du vieux code à partir du nouveau code.
Par exemple, un programmeur peut écrire du code appelant du code réutilisable comme printf(), ....
Avec l'orientation objet, la réutilisation peut aussi être accomplie via l'appel de nouveau code par de l'ancien.
Par exemple, un programmeur peut écrire du code qui est appelé par un framework qui a été écrit par son arrière grand-père. Il n'y a pas
besoin de modifier le code écrit par l'arrière
grand-père. En fait, il n'a même pas besoin d'être recompilé. Et si jamais il ne restait que
le fichier objet, et que le code écrit par l'arrière grand-père ait été perdu depuis 25 ans, cet ancien fichier objet appellera le code avec
les nouvelles fonctionnalités sans rien changer d'autre.
C'est cela l'extensibilité, et c'est cela l'orientation objet.
|
| ||
auteur : Marshall Cline | ||
OUI
Sans les fonctions virtuelles, le C++ ne serait pas un langage orienté objet. La surcharge d'opérateur et les fonctions membres non virtuelles
sont très pratiques, mais ne sont, finalement qu'une variante syntaxique de la notion beaucoup plus classique de passage de pointeur sur une
structure à une fonction. La bibliothèque standard contient de nombreux templates illustrant les techniques de "programmation générique", qui
sont aussi très pratiques, mais les
fonctions virtuelles sont le coeur même de la programmation orientée objet.
D'un point de vue 'business', il y a très peu de raison de passer du C pur au C++ sans les fonctions virtuelles (pour le moment, nous
ferons abstraction de la programmation générique et de la bibliothèque standard). Les spécialistes pensent souvent qu'il a une grande différence
entre le C et le C++ non orienté objet ; mais sans l'orientation objet, la différence n'est pas suffisante pour justifier le coût de
formation des développeurs, des nouveaux outils, ....
En d'autres termes, si je devais conseiller un gestionnaire concernant le passage du C au C++ sans orientation objet (c'est-à-dire changer le langage sans changer de paradigme), je le découragerais probablement, à moins qu'il y ait des contraintes liées aux outils utilisés. D'un point de vue gestion, la programmation orientée objet permet de concevoir des systèmes extensibles et adaptables, mais la syntaxe seule sans l'orientation objet ne réduira jamais le coût de maintenance, mais augmentera probablement les coûts de formation de façon significative.
Nota : le C++ sans virtualité n'est pas orienté objet. Programmer avec des classes mais sans liaison dynamique est une programmation
basée sur des objets, mais n'est pas de la programmation objet. Ignorer la virtualité est équivalent à ignorer l'orientation objet.
Tout ce qui reste est une programmation basée sur des objets, tout comme la version originale d'ADA. (Soit dit en passant, le nouvel ADA
supporte la véritable orientation objet, et non plus simplement la programmation basée sur les objets).
|
| ||
auteurs : LFE, Aurélien Regat-Barrel, Luc Hermitte | ||
Syntaxiquement, une fonction virtuelle pure est une fonction virtuelle suivie de "= 0" dans sa déclaration :
Une fonction virtuelle signifie qu'elle peut être supplantée par une fonction d'une classe fille. Une fonction virtuelle pure signifie qu'elle doit être supplantée par une fonction d'une classe fille. La classe qui déclare une fonction virtuelle pure n'est alors pas instanciable car elle possède au moins une fonction qui doit être supplantée. On dit alors que c'est une classe abstraite (lire Qu'est-ce qu'une classe abstraite ?). Notez que virtuelle pure veut simplement dire que la fonction doit être supplantée par les classes filles que l'on veut instanciables, et non que la fonction n'est pas implémentée. Le C++ autorise une fonction virtuelle pure à disposer d'un corps. Une telle pratique sert généralement à forcer une classe à être abstraite (en rendant son destructeur virtuel pur) ou à proposer une implémentation type pour la fonction virtuelle pure.
|
| |||
auteur : Aurélien Regat-Barrel | |||
Lors de la réimplémentation d'une fonction membre virtuelle dans une classe dérivée, il est possible de ne pas tout à fait respecter le
prototype de la fonction virtuelle en renvoyant un type dérivé du type originel :
Si votre compilateur supporte les retours covariants, alors le message "Derived::Test" devrait s'afficher, et a devrait pointer vers une
instance de B. Si "Base::Test" s'affiche, c'est que le compilateur a considéré que le prototype de Derived::Test ne correspondait pas à
celui de Base::Test et qu'il s'agissait donc d'une nouvelle fonction membre propre à Derived et non pas d'une réimplémentation de
Base::Test (voir Qu'est-ce que le masquage de fonction ?). Dans ce cas, ou en cas de refus de compilation, votre compilateur ne supporte pas les
retours covariants (cas de VC++ 6).
Cette possibilité est en particulier utilisée dans le clonage de classes polymorphiques. Les retours covariants permettent en effet de
transformer le code suivant :
En une version plus élégante sans cast :
Pour plus de détails à ce sujet, lire Comment effectuer la copie d'objets polymorphiques ?.
|
| ||
auteur : Laurent Gomila | ||
Contrairement à ce qu'il se passerait "normalement" avec un appel de
fonction virtuelle, un tel appel effectué depuis le constructeur ou le
destructeur d'une classe de base n'aurait pas l'effet attendu. En effet,
ce sera dans ce cas la fonction de la classe dans laquelle on se trouve
qui sera appelée, et non celle de la dérivée.
La raison est simple : lors de l'appel du constructeur d'une classe de
base, la classe dérivée n'a pas encore été construite. Ainsi un appel
à une fonction de la classe dérivée aurait de fortes chances d'accéder
à des données qui n'existent pas encore. Même explication pour le
destructeur : les données de la classe dérivée ont déjà été détruites.
Voici un exemple qui illustre clairement ce cas de figure :
Résultat :
|
| ||
auteur : JolyLoic | ||
Oui, c'est possible, mais attention, ça ne fait pas toujours ce qu'on pense. La règle
est que le type dynamique d'une variable en cours de construction est celui du
constructeur qui est en train d'être exécuté. Pour bien
comprendre ce qui se passe, il faut donc revenir sur la différence entre le type statique
d'une variable, et son type dynamique.
Prenons par exemple trois classes, C qui dérive de B qui dérive de A. Par exemple, dans :
La variable a possède comme type statique (son type déclaré dans le programme) A*.
Par contre, son type dynamique est B*. Une fonction virtuelle est simplement une
fonction dont on va chercher le code en utilisant le type dynamique de la variable,
au lieu de son type statique, comme une fonction classique.
Maintenant, quand on crée un objet de type C, les choses se passent ainsi :
Donc, dans le corps du constructeur de la classe B, un appel d'une fonction virtuelle
appellera la version de la fonction définie dans la classe B (ou à défaut celle définie
dans A si la fonction n'a pas été définie dans B), et non pas celle définie dans la
classe C.
D'ailleurs, si la fonction est virtuelle pure dans B, ça causera quelques problèmes,
puisqu'on tentera alors d'appeler une fonction qui n'existe pas. En général, le programme
va planter, si on a de la chance, il affichera une message du style "Pure function called".
La problèmatique est exactement la même pour les destructeurs, mais dans l'ordre inverse.
Pourquoi cette règle ? Une fonction définie dans C a accès aux données membre de C.
Or, on a vu que au moment où on exécute l'appel au corps du constructeur de B,
ces dernières ne sont pas encore crées. On a donc préférer jouer la sécurité.
|
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 © 2008 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site ni 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.