Revenons à notre exemple rectangle
et rectplein
pour mettre en lumière un problème inhérent à lhéritage. Nous ne connaissons pas le contenu de la classe rectangle
, mais imaginons quil sagisse simplement des quatre coordonnées h, b, g, d
du rectangle. Dans ce cas, la méthode change
a probablement lallure suivante :
void rectangle::change(int gche, int haut, int drte, int bas){ efface(); if ( (gche >= drte) || (haut >= bas) ) { g = d; return; } // rectangle vide g = gche; d = drte; h = haut; b = bas; trace();}
A priori, il ny a aucune raison de changer cette méthode pour notre classe rectplein
. Pourtant, si lon fait un essai, un appel de change
avec une instance de rectplein
ne donnera pas le bon résultat. Pourquoi ?
Rappelons-nous que la classe rectangle
a été compilée avant la classe rectplein
. Dès lors, lorsque le compilateur, agissant sur le code source de la méthode change
ci-dessus, rencontre un appel à efface
et un autre à trace
, il cherche les méthodes de ce nom ; il nen connaît alors que deux, celles de la classe rectangle
. En mettant les points sur les i, le compilateur « voit » donc ceci :
void rectangle::change(//...){ rectangle::efface(); // ... rectangle::trace();}
On voit dès lors pourquoi cette méthode ne fonctionnera pas correctement avec rectplein
: le rectangle ne sera ni correctement effacé, ni correctement retracé, parce que lon a modifié les méthodes correspondantes dans notre nouvelle classe.
Une solution simple et expéditive consisterait alors à réécrire la méthode change
; seulement voilà, on ne peut pas : nous ne savons pas comment les coordonnées du rectangle sont stockées dans la classe rectangle
, et le saurions-nous, nous ne pourrions pas les modifier puisque les membres correspondants sont inaccessibles dans la classe rectplein
.
Ce problème est classique en programmation orientée objet, et résulte du principe même de compilation. Les langages de POO interprétés comme SmallTalk nont pas de difficultés de cet ordre.
La solution réside dans la déclaration de méthodes virtuelles. Pour les déclarer telles, il suffit de placer le mot réservé virtual
devant le nom de la méthode. Si le programmeur qui a conçu la classe rectangle
était prévoyant, il a compris que certaines des fonctions membres de la classe auraient à être modifiées dans des classes descendantes :
class rectangle { // membres privés public : rectangle(); rectangle(int gche, int haut, int drte, int bas); virtual ~rectangle(); virtual void trace(); virtual void efface(); void valeur(int& gche, int& haut, int& drte, int& bas); void change(int gche, int haut, int drte, int bas); };
Lorsque le compilateur sait quune méthode est virtuelle, il ne place pas un appel direct à cette méthode, mais recherche la dernière méthode redéfinie dans la classe de this
ayant le même nom et les mêmes types darguments, et appelle celle-ci. Il en résulte que la méthode change
aura cette fois-ci le bon comportement : si lon appelle r.change
où r
est de type rectangle
, la méthode appellera les fonctions rectangle::efface
et rectangle::trace
; si lon appelle rp.change
, où rp
est de type rectplein
, la méthode appellera cette fois rectplein::efface
et rectplein::trace
.
On note que les méthodes change
et valeur
nont pas besoin dêtre définies comme virtuelles, parce que leur code na aucune raison dêtre modifié par les classes dérivées de rectangle
(en fait, elles ne peuvent pas le modifier, puisquelles nont pas accès aux coordonnées du rectangle).
Lorsquune méthode est déclarée virtuelle dans une classe, elle lest automatiquement pour toutes les classes dérivées ; il nest donc pas nécessaire de réécrire virtual
devant leur déclaration.
On prendra garde que les méthodes ne sont pas seulement caractérisées par leur nom, mais aussi par la liste de leurs arguments. En conséquence, si lon définit par exemple une méthode rectplein::trace(int couleur)
, celle-ci ne sera pas virtuelle (sauf déclaration explicite) car il sagit dune méthode différente de rectplein::trace()
(en vertu des règles de recouvrement de fonctions vues au chapitre 5) ; de toute façon, ce ne sera pas elle qui sera appelée par rectangle::change
, ne serait-ce que parce que les arguments ne correspondent pas. Précisons quen parlant de la liste des arguments, nous parlons aussi des arguments par défaut : en définissant une unique méthode rectplein::trace(int couleur = -1)
par exemple, on sexpose à des ennuis car la méthode change
appellera alors rectangle::trace()
, seule méthode de ce nom ayant zéro argument dans la classe rectplein
.
Pour éviter tout ennui, on redéfinira une méthode virtuelle avec exactement la même liste darguments, quitte à fournir aussi des homonymes ayant des arguments supplémentaires (ou en moins). On évitera aussi les arguments par défaut dans les méthodes virtuelles, pour la même raison.
Précédent | Sommaire | Suivant |