Les méthodes virtuelles n'ont strictement rien à voir avec les classes virtuelles, bien qu'elles utilisent le même mot clé virtual. Ce mot clé est utilisé ici dans un contexte et dans un sens différent.
Nous savons qu'il est possible de redéfinir les méthodes d'une classe mère
dans une classe fille. Lors de l'appel d'une fonction ainsi redéfinie, la fonction appelée est
la dernière fonction définie dans la hiérarchie de classe. Pour appeler la fonction de la classe mère
alors qu'elle a été redéfinie, il faut préciser le nom de la classe à laquelle elle appartient
avec l'opérateur de résolution de portée (::
).
Bien que simple, cette utilisation de la redéfinition des méthodes peut poser
des problèmes. Supposons qu'une classe B hérite de sa classe mère A.
Si A possède une méthode x
appelant une autre méthode
y
redéfinie dans la classe fille B, que se passe-t-il lorsqu'un objet
de classe B appelle la méthode x
? La méthode appelée étant celle
de la classe A, elle appellera la méthode y
de la classe A.
Par conséquent, la redéfinition de y
ne sert à rien dès qu'on l'appelle à partir
d'une des fonctions d'une des classes mères.
Une première solution consisterait à redéfinir la méthode x
dans la classe B. Mais ce n'est ni élégant, ni efficace. Il faut en fait forcer
le compilateur à ne pas faire le lien dans la fonction x
de la classe A
avec la fonction y
de la classe A. Il faut que x
appelle soit la fonction y
de la classe A si elle est appelée par
un objet de la classe A, soit la fonction y
de la classe B
si elle est appelée pour un objet de la classe B. Le lien avec l'une des méthodes
y
ne doit être fait qu'au moment de l'exécution, c'est-à-dire qu'on doit faire
une édition de liens dynamique.
Le C++ permet de faire cela. Pour cela, il suffit de déclarer virtuelle
la fonction de la classe de base qui est redéfinie dans la classe fille, c'est-à-dire la fonction
y
. Cela se fait en faisant précéder par le mot clé virtual
dans la classe de base.
Exemple 8-25. Redéfinition de méthode de classe de base
#include <iostream> using namespace std; // Définit la classe de base des données. class DonneeBase { protected: int Numero; // Les données sont numérotées. int Valeur; // et sont constituées d'une valeur entière // pour les données de base. public: void Entre(void); // Entre une donnée. void MiseAJour(void); // Met à jour la donnée. }; void DonneeBase::Entre(void) { cin >> Numero; // Entre le numéro de la donnée. cout << endl; cin >> Valeur; // Entre sa valeur. cout << endl; return; } void DonneeBase::MiseAJour(void) { Entre(); // Entre une nouvelle donnée // à la place de la donnée en cours. return; } /* Définit la classe des données détaillées. */ class DonneeDetaillee : private DonneeBase { int ValeurEtendue; // Les données détaillées ont en plus // une valeur étendue. public: void Entre(void); // Redéfinition de la méthode d'entrée. }; void DonneeDetaillee::Entre(void) { DonneeBase::Entre(); // Appelle la méthode de base. cin >> ValeurEtendue; // Entre la valeur étendue. cout << endl; return; }
Si d est un objet de la classe DonneeDetaillee,
l'appel de d.Entre
ne causera pas de problème. En revanche, l'appel de
d.MiseAJour
ne fonctionnera pas correctement, car la fonction
Entre
appelée dans MiseAJour
est la fonction de la classe
DonneeBase, et non la fonction redéfinie dans DonneeDetaille.
Il fallait déclarer la fonction Entre
comme une fonction
virtuelle. Il n'est nécessaire de le faire que dans la classe de base. Celle-ci doit donc être déclarée
comme suit :
class DonneeBase { protected: int Numero; int Valeur; public: virtual void Entre(void); // Fonction virtuelle. void MiseAJour(void); };
Cette fois, la fonction Entre
appelée dans
MiseAJour
est soit la fonction de la classe DonneeBase, si
MiseAJour
est appelée pour un objet de classe DonneeBase, soit celle
de la classe DonneeDetaille si MiseAJour
est appelée pour un objet
de la classe DonneeDetaillee.
En résumé, les méthodes virtuelles sont des méthodes qui sont appelées selon la vraie classe de l'objet qui l'appelle. Les objets qui contiennent des méthodes virtuelles peuvent être manipulés en tant qu'objets des classes de base, tout en effectuant les bonnes opérations en fonction de leur type. Ils apparaissent donc comme étant des objets de la classe de base et des objets de leur classe complète indifféremment, et on peut les considérer soit comme les uns, soit comme les autres. Un tel comportement est appelé polymorphisme (c'est-à-dire qui peut avoir plusieurs aspects différents). Nous verrons une application du polymorphisme dans le cas des pointeurs sur les objets.
Précédent | Sommaire | Suivant |
Des entrées - sorties simplifiées | Niveau supérieur | Dérivation |