Sommaire du chapitre :
class A { public f() ; } ;
class B : public A { public g() ; } ;
A *aa ;
B *bb ;
bb = new B ;
aa = bb ;
aa->f() ;
bb->f() ;
Ci-dessus, on crée un objet qui possède une partie identique à la classe A
et une extension définie par les membres de B.
Si l'on a un pointeur sur l'objet créé par la classe B , on peut imaginer qu'on
a en fait également un pointeur sur un objet de classe A.
C'est effectivement ce qui se passe.
On a créé un objet de classe B et on a déposé ce pointeur dans un pointeur de
classe A .
Cette affectation est légale et le compilateur convertit automatiquement le
type.
La fonction f() étant connue aussi bien dans l'objet aa que dans l'objet bb
, il est possible de l'atteindre à partir de chacun des pointeurs.
Cette disposition permet de créer des librairies qui manipulent un certain nombre de fonctions et d'étendre ces manipulations à tous les objets qui héritent de ces fonctions, ainsi qu'on le verra dans un exemple ci-après.
On appelle ce double accès polymorphisme.
Le fait qu'on accède à une variable par un type hérité est appelé sous-typage.
2. Sous-typage d'objets non pointés
class A { public f() ; } ;
class B : public A { public g() ; } ;
void MaFonction (A &Var) { Var.f() ; }
void main()
{
B bb;
MaFonction(bb) /* bb est converti en A */
}
Partant des mêmes classes que dans l'exemple précédent, on définit une fonction
qui accepte comme paramètre une variable de classe A.
Dans le programme principal, on déclare un objet bb de type B et on appelle
une fonction en lui donnant comme paramètres la variable bb.
Le compilateur convertit automatiquement la variable bb en une variable de classe
A .
La conversion n'est faite que dans ce sens, depuis la classe héritière vers
la classe héritée.
3. Exemple: fifo générique avec ``poignée"
class fifo;
class item { friend class fifo ; item *next; } ; /* Classe
poignee */
class fifo
{
item *first, *last ;
public :
void put(item *m) ;
item *get();
fifo() { first = NULL ; }
} ;
Pour cela on crée un type pointeur défini dans une classe item qu'on héritera
dans les objets qui auront besoin du pointeur pour se maintenir dans une liste.
On appellera cette classe, poignée.
On crée également une classe fifo qui contient des pointeurs de tête et de fin
de liste, ainsi que la fonction put et la fonction get qu'on utilise pour déposer
des éléments dans la liste.
Dans un cas réel, on définirait évidemment encore d'autres fonctions, mais nous
nous concentrons sur le principe ici.
La fonction put doit manipuler le champ next de la classe item pour insérer
un nouvel élément dans la chaîne du fifo.
On a donc indiqué "friend class fifo" de façon que seule la classe
fifo puisse manipuler next.
Si l'on avait mis item en mode privé, la classe fifo ne pourrait l'atteindre
et si on l'avait déclaré public , tout le monde pourrait le manipuler.
Les pointeurs de début et de fin de liste sont initialisés par le constructeur.
Pour qu'on puisse référencer fifo, il faut que la classe fifo soit déclarée,
mais comme elle utilise elle-même item on a simplement fait une déclaration
préalable, la classe étant effectivement décrite en détails plus loin dans la
déclaration.
L'utilisation de ce fifo est illustrée ci-dessous :
class msg : public item { ... } ;
fifo FIFO ;
msg *M;
FIFO.put(M) ;
M = (msg *)FIFO.get();
On a déclaré une classe de message qui hérite item , la poignée.
On a déclaré un en-tête de fifo.
On a déclaré un pointeur sur un message et on montre comment déposer un message
dans le fifo.
La variable M est de type message mais par sous-typage elle est transformée
en type item , ce qui est compatible avec la fonction put.
A la derniere ligne, il faut forcer une conversion de type, car elle n'est
malheureusement pas automatique dans le sens de la classe héritée à la classe
héritière.
Si l'on avait mis un pointeur sur item comme paramètre de get plutôt que retourner
la valeur, on pourrait écrire FIFO.get (& M); et ainsi éviter le cast (forcage
de type).