Sous-typage et Polymorphisme


Sommaire du chapitre :

1. Sous-typage par pointeur

2. Sous-typage d'objets non pointés

3. Exemple: fifo générique avec ``poignée"


1. Sous-typage par pointeur

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.

retour au sommaire


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.

retour au sommaire


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).

retour au sommaire
chapitre suivant