Surcharge de fonctions virtuelles


Sommaire du chapitre :

Introduction

1. Membre virtuel avec surcharge

2. Utilisation de la surcharge

3. Surcharge dans le cas de sous-typage

4. Surcharge avec paramètres différents


Introduction

Une première utilisation du mot-clé virtual a été vue au chapitre 2.
Ce mot-clé permet également de redéfinir des fonctions héritées, ce qui est utile dans deux cas:
- si certains héritiers doivent avoir leur fonction particulière, mais que la plupart se contentent de la même fonction de base.
- s'il faut avoir une fonction d'accès commun à une catégorie d'objets, par sous-typage, mais avec des codes spécialisés pour chaque objet.
La virtualité améliore la réutilisation des objets.
Un programmeur peut préparer des objets et fournir un certain nombre de fonctions par défaut.
L'utilisateur peut les utiliser tels quels ou les redéfinir si nécessaire.
Il peut appeler l'ancienne fonction, à partir de la nouvelle fonction pour la réutiliser mais en la faisant suivre et précéder par du code supplémentaire.
Le mécanisme est expliqué dans les paragraphes qui suivent.

retour au sommaire


1. Membre virtuel avec surcharge

class A
{
public :
virtual void f() ;
virtual m() = 0 ; /* a definir */
} ;
class C : public A
{
public :
void f() ;
void g() { ... A::f() ... }
}
;

La fonction f() est précédée du mot-clé virtual .
Cela indique qu'une classe qui hérite la classe A peut redéfinir elle-même la fonction f() .
La fonction m() doit impérativement être redéfinie dans la classe héritière et avec les mêmes types de paramètres.

En principe, dans la classe A on ne définit que les paramètres de f() et l'on définit la fonction spécialisée dans la classe qui hérite.
On peut ainsi, si l'on a des objets graphiques, des carrés et des triangles par exemple, définir une fonction périmètre qui retourne la longueur du pourtour de l'objet, qui est calculée différemment dans le cas du carré ou du triangle.
On définira plus tard des classes carré et triangle qui auront le code correspondant à leur propre forme, mais l'accès à leur fonction périmètre par sous-typage pourra être effectué indépendamment du type de l'objet.
Dans la classe C, on appelle l'ancienne fonction f() depuis la fonction g().

Elle peut évidemment aussi être appelée depuis la nouvelle fonction f() , ce qui est un moyen pour modifier f() en rajoutant des instructions avant ou après la fonction originale.

retour au sommaire


2. Utilisation de la surcharge

C cc ;
cc.f() ;
/* est equivalent a */
cc.A::f() ;

Ci-dessus, on définit un objet cc de la classe C définie précédemment.
Ensuite, on appelle la fonction f() à partir de cet objet.
On accède dans ce cas à la fonction f() définie dans la classe C.
Il est possible d'accéder à la fonction f() de la classe A en faisant précéder f() de A::.

class A { public : virtual void f() { ... } } ;
class B : public A { } ;

Dans l'exemple ci-dessus, la fonction f() n'a pas été redéfinie dans B, mais on peut l'appeler à partir de la référence d'un objet de classe B , comme l'illustre l'exemple ci-dessous.

B bb;
bb.f() ;
/* est equivalent a */
bb.A::f() ;

retour au sommaire


3. Surcharge dans le cas de sous-typage

Reprenons l'exemple où la fonction f() est redéfinie dans la classe héritière.
On définit des objets correspondant aux classes A et C.
Voyons ce qui se passe si l'on accède C par la partie A et si f() est redéfinie dans C.

A *aa ;
C *cc = new C ;
aa =cc ;
aa->f() ;
cc->f() ;

On met l'adresse de cc dans un pointeur de type A et on appelle la fonction f() à partir de cet objet.
La fonction ayant été redéfinie dans C, on n'appellera plus l'ancienne fonction f(), mais la nouvelle, celle qui est définie dans C.
Qu'on parte de l'objet cc ou de l'objet aa, on atteint la même fonction.

Ainsi quand on surcharge une fonction, c'est la nouvelle fonction qui est accédée, qu'elle soit accédée par la partie héritée ou par la partie qui hérite (et qui surcharge).
Cependant on peut toujours accéder l'ancienne fonction en mettant le nom de l'ancienne classe.

retour au sommaire


4. Surcharge avec paramètres différents

class A { public : virtual void f() { ... } } ;
class B : public A { public void f(int) { ... } } ;
int i;
B bb ;
bb.f(i) ;
bb.f() ;

Lorsque l'on redéfinit la fonction f() , il est possible de changer le type ou le nombre des paramètres.
Dans l'exemple ci-dessus on a défini une fonction f() sans paramètre.
Dans la classe B, qui hérite de A, on a redéfini f() en indiquant un paramètre de type entier.
Si l'on crée un objet bb de classe B , on peut accéder l'une ou l'autre des fonctions, suivant qu'on met le paramètre ou non.

retour au sommaire
chapitre suivant