Vous êtes sur la page 1sur 12

Centre Informatique pour les Lettres

et les Sciences Humaines


Apprendre C++ avec Qt : Leon 14
Fonctions virtuelles et classes abstraites





1 - Redfinition d'un membre hrit ......................................................................................... 2
Redfinition et surcharge.................................................................................................2
Accs par dfaut aux membres propres ...........................................................................2
Utilisation de : : pour accder aux membres hrits........................................................3
Redfinition d'une fonction surcharge dans la classe de base.........................................3
2 - Un polymorphisme indsirable............................................................................................ 4
Redfinition de variables membre....................................................................................4
Redfinition de fonctions membre....................................................................................4
3 - Fonctions virtuelles............................................................................................................. 5
Pourquoi les fonctions membre ne sont-elles pas toutes virtuelles ? ................................6
Valeurs par dfaut des paramtres d'une fonction virtuelle..............................................6
Ce que les fonctions virtuelles rendent possible...............................................................7
4 - Redfinition de fonctions dont le type est li la classe dont elles sont membre.................. 8
5 - Hritage indirect et fonctions virtuelles ............................................................................... 9
Transmission de la virtualit ...........................................................................................9
Absence de redfinition dans certaines classes drives.................................................10
6 - Classes abstraites ............................................................................................................. 10
Fonctions virtuelles pures .............................................................................................10
Version "par dfaut" d'une fonction virtuelle pure..........................................................11
7 - Bon, c'est gentil tout a, mais a fait dj 11 pages. Qu'est-ce que je dois vraiment en
retenir ?............................................................................................................................ 12

Document du 15/01/06 - Retrouvez la version la plus rcente sur http://www.up.univ-mrs.fr/wcpp
C++ - Leon 14 Fonctions virtuelles et classes abstraites 2/12
Nous avons vu (Leon 12) qu'il est possible de traiter indiffremment un ensemble d'objets de
types divers, condition que ces types soient tous drivs d'une mme classe autorisant le
traitement en question. L'intrt de cette technique est accru par la possibilit d'adapter
chacune des classes drives la faon dont est effectu le traitement demand. La demande de
traitement pourra donc tre applique aveuglment une collection htrogne, mme si le
dtail des oprations doit tre adapt au type de chacun des objets concerns.
1 - Redfinition d'un membre hrit
Lorsqu'une classe drive d'une autre, il est possible qu'elle redfinisse certains des membres
qui sont rendus disponibles par l'hritage. Une instance de la classe drive comporte alors
deux membres homonymes : un membre hrit de la classe de base, et un membre propre.

L'usage impose, assez malencontreusement, le terme "redfinition" pour dsigner ce qui n'est en
fait que la dfinition d'un nouveau membre.
Redfinition et surcharge
Comme la surcharge (Leon 10), la redfinition donne naissance des fonctions portant le
mme nom. Ces mcanismes sont cependant bien distincts puisque, dans le cas de la
surcharge, c'est la diffrence des signatures qui permet de rsoudre l'ambigut cre par
l'homonymie, alors que, dans le cas de la redfinition, les signatures sont toujours identiques.

Si une classe drive dfinit une fonction homonyme d'une fonction hrite, mais possdant une
signature diffrente, il ne s'agit pas d'une redfinition mais d'une surcharge. Les mcanismes
dcrits dans la suite de la prsente Leon ne s'appliquent videmment pas dans ce cas.

L'ambigut qui pourrait tre cre par l'homonymie entre un membre hrit et un membre
propre est vite par l'adoption d'une rgle :
Accs par dfaut aux membres propres
L'existence d'un membre propre a pour effet de masquer la prsence du membre hrit portant
le mme nom (un peu comme la dfinition d'une variable locale masque une ventuelle variable
homonyme prcdemment accessible).

Si la classe drive redfinit certains des membres dont elle a hrit, c'est certainement parce
que, pour une raison quelconque, ceux-ci ne lui conviennent pas parfaitement. Il est donc
logique que les manipulations effectues sur une instance de cette classe oprent
prfrentiellement sur les membres qui lui sont propres.

Pour illustrer cette rgle, supposons que nous disposons des dfinitions suivantes :

cl ass CBase 1
{ 2
publ i c: 3
QSt r i ng t ext e; 4
QSt r i ng f ( ) {r et ur n " CBase: : f ( ) " ; } 5
QSt r i ng g( ) {r et ur n " CBase: : g( ) " ; } 6
}; 7

cl ass CDer i vee : publ i c CBase 8
{ 9
publ i c: 10
QSt r i ng t ext e; / / REDEFI NI TI ON d' une var i abl e membr e 11
QSt r i ng f ( ) {r et ur n " CDer i veee: : f ( ) " ; } / / REDEFI NI TI ON d' une f onct i on membr e 12
QSt r i ng h( ) ; 13
}; 14

Une instance de la classe drive comporte donc deux membres nomms val : une variable
hrite de la classe CBase, et une variable propre. La classe CDer i vee possde galement deux
membres nomms f ( ) : une fonction hrite de la classe CBase, et une fonction propre.

Les oprateurs de slection permettent par dfaut l'accs aux membres propres :

CDer i vee unD; 1
CDer i vee *pt r D = &unD; 2
pt r D- >t ext e = " coucou" ; / / l a var i abl e pr opr e CDer i vee r eoi t " coucou" 3
QSt r i ng t = unD. f ( ) ; / / t r ecoi t " CDer i veee: : f ( ) " 4
J-L Pris - 15/01/06
C++ - Leon 14 Fonctions virtuelles et classes abstraites 3/12
De mme, dans le corps d'une fonction propre la classe drive, le nom d'un membre redfini
dsigne le membre propre de l'instance au titre de laquelle la fonction est excute :

QSt r i ng CDer i vee: : h( )
{
t ext e = " CDer i vee: : h( ) appel l e " ;
r et ur n t ext e + f ( ) ; / / r envoi e " CDer i vee: : h( ) appel l e CDer i veee: : f ( ) "
}
Utilisation de : : pour accder aux membres hrits
L'accs aux membres redfinis reste possible l'aide de l'oprateur de rsolution de porte :

unD. CBase: : t ext e = " xx" ; / / l a var i abl e hr i t e de CBase r eoi t l a val eur " xx" 5
t = pt r D- >CBase: : f ( ) ; / / t r ecoi t " CBase: : f ( ) " 6

La redfinition n'empche donc nullement une fonction membre propre d'agir sur les membres
hrits, et h( ) pourrait tre dfinie ainsi :

QSt r i ng CDer i ve: : h( )
{
CBase: : t ext e = CBase: : f ( ) ; / / l a var i abl e hr i t e r eoi t " CBase: : f ( ) "
}

Le fait qu'il reste possible d'utiliser les membres hrits montre bien qu'ils n'ont aucunement t
"redfinis", mais sont simplement masqus par leurs homonymes.
Redfinition d'une fonction surcharge dans la classe de base
Si la classe de base comporte plusieurs fonctions homonymes, la redfinition de l'une d'entre-
elles les masque toutes. Ainsi, aprs

cl ass CB
{
publ i c:
voi d k( ) {} / / une pr emi r e f onct i on k( )
voi d k( i nt ) {} / / une seconde f onct i on k( )
};

cl ass CD : publ i c CB
{
publ i c:
voi d k( ) {} / / r edf i ni t i on d' une des f onct i ons k( )
};

il n'est pas possible d'accder implicitement la fonction hrite non redfinie :

CD uneI nst ance;
uneI nst ance. k( 4) ; / / ERREUR : l a f onct i on hr i t e est masque
uneI nst ance. CB: : k( 4) ; / / OK : appel expl i ci t e de l a f onct i on hr i t e

Une syntaxe particulire permet toutefois de redonner la classe drive son accs implicite
aux fonctions non redfinies et, avec

cl ass CD : publ i c CB
{
publ i c:
usi ng CB: : k;
voi d k( ) ; / / r edf i ni t i on d' une des f onct i ons k( )
};

il serait possible d'crire

CD uneI nst ance;
uneI nst ance. k( 4) ; / / OK : l es ver si ons non r edf i ni es de k( ) sont vi si bl es
J-L Pris - 15/01/06
C++ - Leon 14 Fonctions virtuelles et classes abstraites 4/12
2 - Un polymorphisme indsirable
Pour examiner les consquences de la prsence de membres redfinis, reprenons l'exemple de
la gestion d'une flotte de vhicules de location :

cl ass CVehi cul e 1
{ 2
publ i c: 3
CVehi cul e( ) : pr i x( 0) , di st ance( 0) {} 4
voi d depr eci ee( ) {pr i x *= 0. 9; } / / per t e de 10%de l a val eur 5
voi d acci dent ( ) {depr eci e( ) ; } / / pui sque l e vhi cul e est abi m ! 6
doubl e pr i x; 7
i nt di st ance; 8
}; 9

cl ass CVoi t ur e : publ i c CVehi cul e / / l es CVoi t ur e SONT des CVehi cul e 10
{ 11
publ i c: 12
CVoi t ur e( i nt d = 0) : di st ance( d) {} 13
voi d depr eci e( ) {pr i x *= 0. 85; } / / per t e de 15%de l a val eur 14
i nt di st ance; 15
}; 16
Redfinition de variables membre
Nous savons (Leon 13) qu'un pointeur sur une classe de base (CVehi cul e, par exemple) peut
lgitimement contenir l'adresse d'une instance d'une classe drive (CVoi t ur e, par exemple),
puisqu'un tel objet est un cas particulier d'instance de la classe de base. Toutefois, un pointeur
sur la classe de base ne permet d'accder qu'aux membres hrits, et son usage conduit donc
violer la rgle d'accs par dfaut aux membres propres. Lorsqu'une variable membre est
redfinie, l'utilisation d'un pointeur sur la classe de base contenant l'adresse d'une instance de
la classe drive donne donc lieu un effet paradoxal :

CVoi t ur e t i t i ne ( 45312) ; / / el l e a dj r oul 1
CVoi t ur e * pt r D = &t i t i ne; 2
CVehi cul e * pt r B = pt r D; / / nos deux poi nt eur s cont i ennent l a mme adr esse, 3
i nt dUn = pt r B- >di st ance; / / mai s dUn vaut 0 ( l a di st ance hr i t ) al or s 4
i nt dDeux = pt r D- >di st ance; / / que dDeux vaut 45312 ( l a di st ance pr opr e) 5

Le constructeur de CVoi t ur e n'initialise pas sa partie hrite, qui est donc construite avec le
constructeur par dfaut de CVehi cul e , ce qui donne la distance hrite une valeur nulle.

Le mme objet (t i t i ne) prsente donc des caractristiques diffrentes selon le type du
pointeur qui sert l'observer (elle a l'air bien plus fatigue si elle est vue comme une CVoi t ur e
que si elle est vue comme un simple CVehi cul e).

D'aprs le "Trsor de la Langue Franaise", ce qui est polymorphe "offre des apparences, des
formes diverses". Une instance d'une classe drive mrite cette pithte dans la mesure o elle
ne prsente pas les mmes caractristiques selon le type de pointeur utilis pour l'observer (elle
a donc des apparences diverses). Une classe de base, pour sa part, peut avoir des instances de
types diffrents, donc des formes diverses. Elle peut donc, elle aussi, tre dite polymorphe.

Inutile de dire que, dans ces conditions, le dveloppement d'un programme cohrent devient
une entreprise trs hasardeuse. Nous pouvons en conclure que

Il ne faut jamais redfinir les variables membre.
Redfinition de fonctions membre
Lorsqu'une fonction redfinie est invoque au titre d'une instance dsigne par un pointeur, un
polymorphisme analogue celui dcrit propos des variables membre peut tre observ. Selon
le type du pointeur dsignant l'instance, ce n'est en effet pas le mme code qui est excut :

pt r B- >depr eci e( ) ; / / appel de l a f onct i on hr i t e : l e pr i x per d 10% 6
pt r D- >depr eci e( ) ; / / appel de l a f onct i on pr opr e : l e pr i x per d 15% 7

J-L Pris - 15/01/06
C++ - Leon 14 Fonctions virtuelles et classes abstraites 5/12
Essayer d'crire un programme dans ces conditions serait un peu comme travailler avec un
collaborateur dont les comptences varieraient selon s'il est interpell par son nom ou par son
prnom. Ce n'est peut-tre pas totalement impossible, mais le risque de malentendu est lev.

Mme si aucune instance de la classe drive n'est explicitement dsignes par un pointeur, il
suffit qu'une fonction hrite appelle une fonction redfinie pour que le problme surgisse.
Dans notre exemple, la fonction acci dent ( ) n'est pas redfinie dans la classe drive. Cette
fonction excutera donc les mmes oprations, que l'instance au titre de laquelle elle est
excute soit de type CVehi cul e ou de type CVoi t ur e :

t i t i ne. acci dent ( ) ; / / son pr i x ne per d pas 15%, mai s seul ement 10%! 8

En clair, les comportements spcifiques d'une classe drive sont inaccessibles ses fonctions
non spcifiques

Le langage C++ prvoit toutefois un mcanisme qui permet d'liminer les paradoxes lis la
redfinition des fonctions membre.
3 - Fonctions virtuelles
Lorsqu'une fonction dclare virtuelle dans la classe de base est invoque au titre d'une
instance dsigne par un pointeur (ou d'une rfrence) sur la classe de base, c'est le type de
l'objet dsign par le pointeur (ou la rfrence) et non le type de ce pointeur (ou de cette
rfrence) qui dtermine quelle fonction est excute. S'il s'agit d'une instance d'une classe
drive, c'est la fonction propre cette classe qui sera excute. La "virtualit" permet donc de
retrouver la cohrence ncessaire la manipulation des objets concerns. Par consquent,

Il ne faut redfinir que des fonctions dclares virtuelles dans la classe de base.

Ainsi, aprs

cl ass CVehi cul e 1
{ 2
publ i c: 3
CVehi cul e( ) : pr i x( 0) , di st ance( 0) {} 4
vi r t ual voi d depr eci ee( ) {pr i x *= 0. 9; } / / per t e de 10%de l a val eur 5
voi d acci dent ( ) {depr eci e( ) ; } / / pui sque l e vhi cul e est abi m ! 6
doubl e pr i x; 7
i nt di st ance; 8
}; 9

cl ass CVoi t ur e : publ i c CVehi cul e / / l es CVoi t ur e SONT des CVehi cul e 10
{ 11
publ i c: 12
CVoi t ur e( i nt d = 0) : di st ance( d) {} 13
voi d depr eci e( ) {pr i x *= 0. 85; } / / per t e de 15%de l a val eur 14
}; 15

on aura bien

CVoi t ur e choupet t e; 1
CVehi cul e *unVehi cul e = &choupet t e; 2
unVehi cul e- >depr eci e( ) ; / / choupet t e per d 15%de sa val eur 3

De plus, les fonctions de la classe de base appellent la bonne version d'une fonction virtuelle
lorsqu'elles sont elles-mme excutes au titre d'une instance de la classe drive :

choupet t e. acci dent ( ) ; / / el l e per d 15%de sa val eur 4

Lorsqu'une fonction membre en appelle une autre, elle utilise implicitement le pointeur t hi s.
Dans le cas de la fonction acci dent ( ) , ce pointeur est de type CVehi cul e *, mais comme la
fonction est ici excute au titre d'une instance de CVoi t ur e, il contient l'adresse de cet objet.
L'invocation d'une fonction virtuelle (depr eci e( ) , en l'occurrence) se traduira donc bien par
l'excution du code propre CVoi t ur e et, donc, par une perte de 15% de la valeur.
J-L Pris - 15/01/06
C++ - Leon 14 Fonctions virtuelles et classes abstraites 6/12
Pourquoi les fonctions membre ne sont-elles pas toutes virtuelles ?
Les fonctions virtuelles exigent du compilateur un vritable tour de force. Le premier des
exemples proposs ci-dessus est assez simple, parce que l'examen du code rvle l'vidence
que l'objet dsign par unVehi cul e est une instance de la classe CVoi t ur e. Le second exemple
est bien plus pineux, car acci dent ( ) doit appeler une fonction depr eci e( ) diffrente selon
si elle est elle-mme excute au titre d'une instance de CVehi cul e ou d'une instance de
CVoi t ur e. Comme il est possible qu'un programme invoque la fonction acci dent ( ) tantt au
titre d'une CVoi t ur e, tantt au titre d'un CVehi cul e, il est clair que le choix ne peut tre
effectu lors de la compilation. Il faut donc que le compilateur gnre du code permettant de
diffrer cette dcision, qui devra tre prise " la vole", lors de chaque excution de la fonction.

Si l'on prend en compte le fait que les classes drives concernes peuvent fort bien ne pas
encore avoir t dfinies au moment de la compilation du code appelant la fonction virtuelle, on
se rend compte que l'excution d'une fonction virtuelle n'est pas un exercice anodin. Le
mcanisme qui rend cette excution possible est connu sous le nom de "dynamic binding", et il
a un cot significatif en termes de vitesse d'excution. C'est ce cot qui explique que toutes les
fonctions membre n'adoptent pas automatiquement ce mode de fonctionnement : les
concepteurs du langage n'ont pas voulu faire payer chaque appel de fonction un luxe qui
n'est apprciable que dans certains cas.

Il existe cependant une fonction dont la "non-virtualit" pose souvent des problmes : il s'agit
du destructeur fourni invisiblement par le langage. Comme nous l'avons vu dans la Leon 12,
la destruction d'une instance est souvent ordonne en dsignant celle-ci l'aide d'un pointeur.
Si ce pointeur est un pointeur sur une classe de base contenant l'adresse d'une instance d'une
classe drive et que le destructeur de la classe de base n'est pas virtuel, c'est lui qui sera
excut et seule la partie de l'instance dfinie par hritage sera dtruite correctement.

Le destructeur d'une classe de base doit toujours tre virtuel.

C'est pour cette raison que certains environnement de dveloppement (Visual C++, par exemple)
ajoutent systmatiquement la dfinition d'un destructeur lorsqu'il gnrent le squelette d'une
nouvelle classe : le corps de cette fonction reste le plus souvent vide, mais il est ncessaire de la
dfinir explicitement pour pouvoir la rendre virtuelle...

Remarquez que, bien que les destructeurs des classes drives ne soient pas rellement des
redfinition de celui de la classe de base (les destructeurs ne sont pas hritables et ces
fonctions ont chacune un nom diffrent), la virtualit du destructeur de la classe de base a
les mmes consquences que si c'tait le cas : les destructeurs des classes drives deviennent
automatiquement virtuels et le polymorphisme de la destruction est gr correctement.
Valeurs par dfaut des paramtres d'une fonction virtuelle
Lorsqu'une fonction virtuelle dispose de valeurs par dfaut pour ses paramtres, le dynamic
binding ne garantit pas l'utilisation des valeurs par dfaut adquates si celles-ci sont modifies
lors de la redfinition de la fonction.

Pourquoi ? Par souci d'efficacit. Rendre sre la redfinition des valeurs par dfaut entranerait
une complexit supplmentaire qui imposerait un ralentissement de tous les appels de fonctions
virtuelles. On a, l encore, prfr sacrifier sur l'autel de l'intrt gnral les quelques cas o la
gestion correcte de la redfinition des valeurs par dfaut aurait prsent un rel avantage.

Ainsi, aprs

cl ass CB 1
{ 2
publ i c: 3
vi r t ual i nt uneFonct i on( i nt p = 2) {r et ur n p; } / / f onct i on membr e vi r t uel l e 4
}; 5
cl ass CD : publ i c CB 6
{ 7
publ i c: 8
i nt uneFonct i on( i nt p = 3) {r et ur n p; } / / REDEFI NI TI ON 9
}; 10

on aura une nouvelle manifestation du "polymorphisme indsirable" :
J-L Pris - 15/01/06
C++ - Leon 14 Fonctions virtuelles et classes abstraites 7/12
CD unCD; 1
CB * pt r CB = & unCD; / / l es deux poi nt eur s cont i ennent 2
CD * pt r CD = & unCD; / / l a mme adr esse, et pour t ant 3
i nt c = pt r CB- >uneFonct i on( ) ; / / c vaut 2 4
i nt d = pt r CD- >uneFonct i on( ) ; / / al or s que d vaut 3 ! 5

C'est bien la fonction propre la classe CD qui est invoque la ligne 4 (puisque
uneFonct i on( ) est virtuelle). Toutefois, lorsqu'elle est appele via un pointeur sur CB, cette
fonction n'utilise pas sa propre valeur par dfaut, mais celle dfinie pour la fonction hrite.

Quand on redfinit une fonction virtuelle, il ne faut jamais doter ses paramtres de valeurs par
dfaut diffrentes de celles adoptes dans la classe de base.
Ce que les fonctions virtuelles rendent possible
Si la redfinition n'est pratique que sur des fonctions virtuelles, le langage gre correctement
le polymorphisme, c'est dire qu'il le masque totalement aux programmeurs.

Les instances de la classe de base qui sont des instances de diffrentes classes drives ne sont
pas identiques, puisqu'elles possdent des membres propres diffrents. Un programmeur qui
manipule une collection htrogne d'instances de la classe de base n'a pas se soucier de ce
polymorphisme, car le dynamic binding lui garantit l'excution du code le plus adapt. Lorsqu'on
dit que C++ "permet le polymorphisme", on fait allusion cette gestion correcte du
polymorphisme des classes de base, et non au fait que la redfinition irraisonne de membres
hrits peut crer des situations o le mme objet donne, selon comment on le regarde,
l'impression d'tre dans des tats diffrents...

Imaginons, par exemple, deux classes ainsi dfinies :

cl ass CBase 1
{ 2
publ i c: 3
i nt a; 4
vi r t ual voi d i ni t i al i sat i on( ) {a = 4; } 5
}; 6

cl ass CDer i vee : publ i c CBase 7
{ 8
publ i c: 9
i nt b; 10
voi d i ni t i al i sat i on( ) {b = 5; CBase: : i ni t i al i sat i on( ) ; } 11
}; 12

La classe CBase ne possde qu'une variable membre, dont on suppose que la valeur 4
correspond une reprsentation d'un quelconque "tat initial" d'un objet. On suppose
galement que le programme exige que cet tat initial puisse tre instaur par une fonction
autre qu'un constructeur, en l'occurrence la fonction i ni t i al i sat i on( ) .

CDer i vee est une spcialisation de CBase qui possde un membre supplmentaire qui doit,
dans l'tat "initial", adopter 5 pour valeur. La fonction i ni t i al i sat i on( ) propre CDer i vee
procde donc l'affectation de cette valeur au membre propre, puis appelle la fonction
i ni t i al i sat i on( ) hrite de CBase, de faon ce que le membre hrit reoive, lui aussi, la
valeur dsire.

Un appel la fonction hrite est prfrable un accs direct la variable hrite, car, en cas
d'volution de la classe de base, il sera plus simple de n'avoir modifier que la fonction
d'initialisation qui y est dfinie que d'avoir mettre jour les fonctions d'initialisation de toutes
les classes drives.

La virtualit de la fonction i ni t i al i sat i on( ) permet de replacer tous les objets d'une
collection htrogne dans leur tat "initial" sans s'inquiter des diffrences de type qui exigent
que des traitements diffrents soient provoqus par des appels apparemment identiques :

CBase obj et Un; 1
CDer i vee obj et Deux; 2
QVal ueLi st <CBase *> l i st eDePt r ; 3
l i st eDePt r . append( &obj et Un) ; 4
l i st eDePt r . append( &obj et Deux) ; / / une col l ect i on ht r ogne 5
J-L Pris - 15/01/06
C++ - Leon 14 Fonctions virtuelles et classes abstraites 8/12
QVal ueLi st <CBase *>: : I t er at or i t ; 6
f or ( i t = l i st eDePt r . begi n( ) ; i t ! = l i st eDePt r . end( ) ; ++i t ) 7
( *i t ) - >i ni t i al i sat i on( ) ; / / une i ni t i al i sat i on " pol ymor phe" 8
4 - Redfinition de fonctions dont le type est li la classe dont
elles sont membre
Lorsqu'une fonction virtuelle est redfinie, elle doit conserver la mme signature (faute de quoi
il s'agit d'une surcharge) et le mme type (sous peine de dclencher une erreur de compilation
du genre "overriding function differs only by return type").

Pourquoi une telle exigence ? La redfinition des fonctions virtuelles est destine permettre
leur invocation indiffrencie, comme dans l'exemple de la rinitialisation des lments d'une
collection htrogne propos ci-dessus. Dans ce contexte, on voit mal comment le code
appelant pourrait exploiter les valeurs ventuellement renvoyes par les fonctions redfinies si
ces valeurs taient de types compltement diffrents.

Il existe pourtant des types que le code appelant accepte de ne pas diffrencier : les pointeurs
(ou rfrences) sur la classe de base et les pointeurs (ou rfrences) sur des classes drives. Le
langage C++ admet donc ce que les anglophones appellent des "covariant return types" :

Si une fonction membre d'une classe de base renvoie un pointeur (resp. une rfrence) sur une
instance de cette classe, elle peut tre redfinie dans une classe drive par une fonction
renvoyant un pointeur (resp. une rfrence) sur une instance de cette classe drive.

Ceci n'est qu'une relativisation de la rgle : les types renvoys n'ont pas tre littralement les
mmes, il suffit qu'ils soient forms de la mme faon partir du nom de la classe concerne.

cl ass CBase 1
{ 2
publ i c: 3
vi r t ual doubl e i nt er di t ( ) {r et ur n 2. 5; } 4
vi r t ual CBase * aut or i se( ) {r et ur n t hi s; } 5
}; 6

cl ass CDer i vee : publ i c CBase 7
{ 8
publ i c: 9
/ / i nt i nt er di t ( ) {r et ur n 3; } / / pr ovoquer ai t une er r eur de compi l at i on ! 10
CDer i vee * aut or i se( ) {r et ur n t hi s; } / / ok pour un compi l at eur I SO 11
}; 12

Microsoft Visual C++ 6.0 ne connat pas les covariant return types. Un compilateur plus rcent
(Visual C++ 2005, par exemple) est donc ncessaire pour exploiter cet aspect du langage.

La relaxation de l'exigence d'identit des types permet notamment de rendre virtuelles les
fonctions prenant en charge les oprateurs ++et - - prfixs, qui renvoient (cf. Leon 11) une
rfrence l'objet sur lequel ils ont opr :

cl ass CBase 1
{ 2
publ i c: 3
CBase( ) : m_a( 0) {}; 4
vi r t ual CBase & oper at or ++( ) {++m_a; r et ur n *t hi s; } 5
const CBase oper at or ++( i nt ) {CBase t emp( *t hi s) ; ++*t hi s; r et ur n t emp; } 6
i nt m_a; 7
}; 8

cl ass CDer i vee : publ i c CBase 9
{ 10
publ i c: 11
CDer i vee( ) : m_b( 0) {} 12
CDer i vee & oper at or ++( ) {CBase: : oper at or ++( ) ; ++m_b; r et ur n *t hi s; } 13
i nt m_b; 14
}; 15

J-L Pris - 15/01/06
C++ - Leon 14 Fonctions virtuelles et classes abstraites 9/12
Remarquez que les fonctions prenant en charge l'usage postfix de ces oprateurs ne
sauraient, pour leur part, tre rendues virtuelles. Ces fonctions doivent en effet renvoyer la
valeur qu'avait l'objet avant l'opration, et non un pointeur ou une rfrence.

Il suffit d'imaginer un contexte d'utilisation de ces fonctions pour se rendre compte que leur
virtualit n'aurait, de toute faon, aucun intrt. En effet, disposer d'un pointeur (ou d'une
rfrence) dsignant un objet d'un type imparfaitement connu peut avoir un intrt, car cela
permet d'invoquer toutes les fonctions de l'interface de la classe de base. Disposer d'une valeur
d'un type imparfaitement connu ne sert par contre rien, puisqu'on ne peut strictement rien
faire de cette valeur, pas mme l'affecter un objet (de quel type d'objet pourrait-il s'agir ?).

Dans bien des cas, cependant, les oprateurs ++et - - sont utiliss uniquement pour leur effet,
sans que la valeur de l'expression intervienne en quoi que ce soit dans le droulement du
programme. Les versions pr et postfixe sont alors perues comme essentiellement
quivalentes et, si la premire est virtuelle, cette quivalence doit tre maintenue :

CDer i vee maVar i abl e; / / maVar i abl e. a et maVar i abl e. b val ent 0 1
CBase & al i as = maVar i abl e; 2
++al i as; / / maVar i abl e. a et maVar i abl e. b passent 1 3
al i as++; / / maVar i abl e. a et maVar i abl e. b passent 2 4

La fonction CBase: : oper at or ++( i nt ) n'tant pas virtuelle, c'est elle qui est appele lorsque la
ligne 4 est excute, en dpit du fait que al i as dsigne un objet de type CDer i vee. Toutefois,
comme cette fonction n'incrmente pas elle-mme m_a mais appelle CBase: : oper at or ++( ) ,
celle-ci est excute au titre de l'instance dsigne par t hi s. Bien que ce pointeur soit de type
CBase *, il contient l'adresse d'une instance de CDer i vee (maVar i abl e, en l'occurrence).
Comme CBase: : oper at or ++( ) est virtuelle, c'est en fait CDer i vee: : oper at or ++( ) qui est
excute, ce qui se traduit par l'incrmentation de m_a (via un appel CBase: : oper at or ++( ) au
titre d'un objet de type CBase) et par celle de m_b. La valeur de l'expression al i as++ reste
cependant de type CBase, et n'est donc pas celle de maVar i abl e avant incrmentation...

Il est souvent prfrable d'implmenter les fonctions prenant en charge l'usage postfix de - - et
de ++en appelant les fonctions prenant en charge l'usage prfix de ces oprateurs.
5 - Hritage indirect et fonctions virtuelles
Les mcanismes de redfinition et de dynamic binding ne se limitent videmment pas
l'hritage direct. En cas d'hritages en cascade, les questions de transmission de la virtualit et
d'absence de redfinition de certaines fonctions doivent tre envisages.
Transmission de la virtualit
Lorsqu'une classe drive redfinit une fonction dclare virtuelle dans la classe de base, cette
fonction reste virtuelle, mme si cela n'est pas rpt lors de la redfinition. Ainsi, aprs

cl ass CMer e 1
{ 2
publ i c: 3
i nt a; 4
vi r t ual voi d i ni t i al i sat i on( ) {a = 4; } 5
}; 6

cl ass CFi l l e : publ i c CMer e 7
{ 8
publ i c: 9
i nt b; 10
voi d i ni t i al i sat i on( ) { CMer e: : i ni t i al i sat i on( ) ; b = 5; } / / vi r t uel l e ! 11
}; 12

les deux variables membre de uneFi l l e seront correctement traites par

CFi l l e uneFi l l e;
CMer e * pt r = &al i ce;
pt r - >i ni t i al i sat i on( ) ; / / appel de l a f onct i on pr opr e CPet i t eFi l l e

Toutefois, cette propagation automatique de la virtualit ne favorise pas la lisibilit du code
source, puisque le seul examen de la classe CFi l l e ne permet pas d'apprhender une des
J-L Pris - 15/01/06
C++ - Leon 14 Fonctions virtuelles et classes abstraites 10/12
caractristiques essentielles de cette hirarchie (la virtualit de la fonction). Il est donc
prfrable de rpter le mot vi r t ual tous les niveaux de la hirarchie, mme si cela ne
change rien au fonctionnement du programme.
Absence de redfinition dans certaines classes drives
Lorsque, dans une hirarchie de classes, certaines classes drives ne redfinissent pas une
fonction virtuelle, l'appel de la fonction en question au titre de l'une de leurs instances
provoque l'excution de la fonction hrite. Rien ne s'oppose ce qu'une classe drive d'une
telle classe reprenne l'initiative et redfinisse sa propre version de la fonction :

cl ass CMer e 1
{ 2
publ i c: 3
i nt a; 4
vi r t ual voi d i ni t i al i sat i on( ) {a = 4; } 5
}; 6

cl ass CFi l l e : publ i c CMer e 7
{ / / cet t e cl asse ne r edf i ni t pas l a f onct i on i ni t i al i sat i on( ) 8
}; 9

cl ass CPet i t eFi l l e : publ i c CFi l l e 10
{ 11
publ i c: 12
i nt b; 13
vi r t ual voi d i ni t i al i sat i on( ) { CFi l l e: : i ni t i al i sat i on( ) ; b = 5; } 14
}; 15

Dans cet exemple, la classe CFi l l e ne dispose que d'une seule fonction i ni t i al i sat i on( ) ,
celle qu'elle hrite de la classe CMer e et c'est donc cette fonction qui est excute la suite de
l'appel de la ligne 14. La "chane de la virtualit" n'est aucunement brise par ce "chanon
manquant" :

CPet i t eFi l l e al i ce; 1
CMer e * pt r = &al i ce; 2
pt r - >i ni t i al i sat i on( ) ; / / appel de l a f onct i on pr opr e CPet i t eFi l l e 3
6 - Classes abstraites
L'utilisation du mcanisme d'hritage conduit souvent dfinir des classes qui ne servent qu'
fournir un moyen de manipuler de faon indiffrencie des instances de diverses classes
drives. De telles classes ne sont pas destines tre elles-mme directement instancies,
mais seulement servir de classes de base d'autres classes (qui, elles, seront instancies).

Lorsqu'une classe de base est dans ce cas, il peut arriver que certaines fonctions doivent y tre
dfinies (parce qu'elles assurent des traitements qui doivent pouvoir tre appliqus des
collections htrognes d'instances de classes drives) sans qu'il soit possible de leur donner
un corps (parce que les traitements impliquent d'accder des membres propres aux classes
drives). Le langage C++ satisfait ces exigences contradictoires en autorisant la cration de
Fonctions virtuelles pures
La syntaxe utilise pour crer une fonction virtuelle pure est trs particulire : elle exige que la
parenthse clturant la liste des paramtres soit suivie du signe =, du nombre 0 et d'un point
virgule en lieu et place du bloc de code dfinissant le corps d'une fonction ordinaire.

La prsence d'une fonction virtuelle pure dans une classe a deux consquences :
- la classe en question ne peut plus tre instancie directement ;
- toute classe drivant de cette classe doit normalement redfinir cette fonction.

Lorsqu'une classe comporte une fonction virtuelle pure, elle peut tre qualifie d'abstraite,
puisqu'elle ne peut donner naissance aucune instance. Les classes abstraites sont, par
nature, destines servir de classes de base. Certains auteurs les dsignent donc parfois en
utilisant l'acronyme ABC (pour Abstract Base Class).

J-L Pris - 15/01/06
C++ - Leon 14 Fonctions virtuelles et classes abstraites 11/12
L'exemple traditionnel de mise en uvre d'une classe abstraite concerne des formes
gomtriques. C'est un bon exemple, parce qu'il est facile d'imaginer une situation o un
programme aurait besoin de parcourir un ensemble de pices de formes diverses pour, par
exemple, faire la somme de leurs surfaces. Le parcours de cet ensemble peut tre effectu
facilement l'aide d'un pointeur sur une classe CFor me, la seule condition que toutes les
classes dcrivant des objets susceptibles d'apparatre dans la collection soient drives de
CFor me. Pour que le calcul de surface puisse tre fait par l'intermdiaire d'un pointeur sur
CFor me, il faut que cette classe dispose d'une fonction sur f ace( ) . Toutefois, le calcul de la
surface d'une CFor me n'a gure de sens : on sait, par exemple, calculer la surface d'un
CRect angl e ou d'un CDi sque en fonction de leurs dimensions, mais la notion de forme est trop
abstraite pour se prter une vritable dfinition du corps de la fonction CFor me: : sur f ace( ) .
Nous pouvons donc dfinir ainsi notre classe de base :

cl ass CFor me / / une ABC 1
{ 2
publ i c: 3
vi r t ual doubl e sur f ace( ) = 0; / / une f onct i on vi r t uel l e pur e 4
}; 5

Bien que la classe CFor me comporte comme membre unique une fonction dpourvue de corps,
son existence est loin d'tre inutile, car elle permet la gestion correcte du polymorphisme :

cl ass CDi sque : publ i c CFor me 1
{ 2
publ i c: 3
CDi sque( doubl e r ) : r ayon( r ) {} 4
vi r t ual doubl e sur f ace( ) { r et ur n 3. 14 * r ayon * r ayon; } 5
pr ot ect ed: 6
doubl e r ayon; 7
}; 8

cl ass CRect angl e : publ i c CFor me 9
{ 10
publ i c: 11
CRect angl e( doubl e l , doubl e L) : l ar geur ( l ) , l ongueur ( L) {} 12
vi r t ual doubl e sur f ace( ) { r et ur n l ar geur * l ongueur ; } 13
pr ot ect ed: 14
doubl e l ar geur ; 15
doubl e l ongueur ; 16
}; 17

Le seul rapport concret entre les classes CDi sque et CRect angl e est le fait que toutes deux
disposent d'une fonction sur f ace( ) . Ces fonctions sur f ace( ) ont la mme signature, mais
c'est leur seul point commun, puisque les calculs qu'elles effectuent ne font intervenir que des
donnes propres leurs classes respectives. Un rapport syntaxique subtil est toutefois tabli
entre ces fonctions : elles sont toutes deux des redfinitions d'une fonction virtuelle pure
dclare dans la classe dont CDi sque et CRect angl e drivent. La raison d'tre de la classe
CBase est d'tablir ce rapport syntaxique qui va, par la suite, nous permettre d'appeler ces
deux fonctions trs diffrentes exactement comme s'il s'agissait d'une seule et mme fonction.

Faire de CBase une classe abstraite prsente ici deux avantages : cela rsout de faon simple la
question du corps de la fonction CFor me: : sur f ace( ) , et cela indique clairement au lecteur
humain que la classe CBase n'existe que pour permettre l'utilisation du polymorphisme.
Version "par dfaut" d'une fonction virtuelle pure
La prsence d'une fonction virtuelle pure garantit que la classe ne pourra pas tre instancie
directement. Lorsqu'un ensemble de classes est conu pour tre mis la disposition d'autres
programmeurs (sous la forme d'une librairie, par exemple), cette garantie constitue une
protection radicale contre une utilisation inadapte de classes qui, comme la classe CFor me
voque ci-dessus, ne sont destines qu' permettre le polymorphisme.

Les utilisateurs d'une classe abstraite doivent cependant payer cette scurit en acceptant de
redfinir les fonctions virtuelles pures dans toutes les classes qu'ils drivent de telles classes
de bases. Il arrive que certaines classes drives puissent trs bien s'accommoder d'une
J-L Pris - 15/01/06
C++ - Leon 14 Fonctions virtuelles et classes abstraites 12/12
"version gnrique" de la fonction, et avoir recopier un bloc de code identique dans toutes les
classes est non seulement fastidieux mais aussi source de difficults de maintenance. Il est
alors possible de donner la fonction virtuelle pure un corps dans la classe de base.

Si, par exemple, nous envisageons de crer des classes drives de CFor me pour lesquelles le
calcul de la surface n'est pas pertinent, nous pouvons dfinir ainsi notre classe de base :

cl ass CFor me / / une ABC 1
{ 2
publ i c: 3
vi r t ual doubl e sur f ace( ) = 0 {r et ur n - 1; } / / vi r t uel l e pur e avec un cor ps 4
}; 5

Dans ces conditions, la classe CFor me reste abstraite (elle ne peut tre instancie directement),
mais les classes qui en sont drives n'ont pas redfinir la fonction sur f ace( ) si celle-ci ne
les concerne pas : elles hritent de la "version par dfaut" dfinie dans la classe de base.

Le fait que la syntaxe du langage permette de procder ainsi ne doit pas tre interprt comme
une incitation le faire. J'ai personnellement tendance penser que, si une classe drive n'est
"pas concerne" par une fonction virtuelle de la classe de base, il y a probablement une erreur
d'analyse quelque part. Remarquez, en outre, qu'une classe hritant de la version "par dfaut"
d'une fonction virtuelle pure reste elle-mme abstraite...
7 - Bon, c'est gentil tout a, mais a fait dj 11 pages. Qu'est-ce
que je dois vraiment en retenir ?
1) Une classe drive peut comporter un membre propre homonyme l'un des membres dont
elle hrite. On dit alors (un peu la lgre) qu'elle redfinit ce membre hrit.

2) En cas de redfinition, on accde par dfaut au membre propre.

3) Il ne faut JAMAIS redfinir une fonction non virtuelle ou une variable.

4) Le destructeur d'une classe qui risque de servir de classe de base doit tre virtuel.

Indice : si une classe dfinit une fonction virtuelle, elle va sans doute servir de classe de base.

5) La redfinition d'une fonction surcharge masque toutes les fonctions hrites homonymes.

6) Lorsqu'une fonction est virtuelle, son invocation via un pointeur (ou une rfrence) sur la
classe de base se traduit par l'excution du code disponible le plus adapt au type de l'objet
dsign par le pointeur (ou la rfrence).

Cas particulier important : lorsqu'une fonction membre en appelle une autre, elle le fait
(implicitement) via un pointeur (t hi s). Qu'elle soit ou non virtuelle, une fonction membre appelle
donc toujours la bonne version des fonctions virtuelles de sa classe.

7) La redfinition d'une fonction hrite ne doit pas spcifier pour ses paramtres des valeurs
par dfaut diffrentes de celles utilises par la classe de base.

8) Une fonction virtuelle pure est habituellement dpourvue de corps dans la classe de base.

9) Les fonctions virtuelles pures ne servent que d'interface pour excuter les redfinitions qu'en
font les classes drives.

10) Si une classe contient une fonction virtuelle pure, elle ne peut plus tre instancie. C'est ce
qu'on appelle une classe abstraite.

11) Si une classe drive d'une classe abstraite n'en redfinit pas toutes les fonctions virtuelles
pures, elle reste elle-mme abstraite.
J-L Pris - 15/01/06

Vous aimerez peut-être aussi