POLYMORPHISME 1 LE POLYMORPHISME... 1 1.1 PRÉSENTATION DU PRINCIPE À PARTIR D UN EXEMPLE... 1 1.2 INTÉRÊT... 2 2 LES LIMITES DE L HÉRITAGE... 2 3 IMPLÉMENTATION DU POLYMORPHISME EN LANGAGE C++... 3 3.1 LES FONCTIONS VIRTUELLES... 3 3.2 EXEMPLE DE PROGRAMME... 3 4 TYPAGE DU DESTRUCTEUR... 4 4.1 MISE EN ŒUVRE DU DESTRUCTEUR PAR DÉFAUT... 4 4.2 LE DESTRUCTEUR TYPÉ VIRTUEL... 4 4.2.1 1 : Programme mettant en œuvre des instances de classes dérivée à partir d un pointeur typé classe de base... 4 4.2.2 2 : Programme passant en paramètre à une fonction une instance d une classe dérivée dont l argument est typé classe de base... 4 4.3 REMARQUES... 4 5 IMPLANTATION DES FONCTIONS VIRTUELLES EN MÉMOIRE... 5 6 CLASSE ABSTRAITE... 5 6.1 DÉFINITION ET MÉTHODE VIRTUELLE PURE... 5 6.2 CONTRAINTES D UTILISATION D UNE CLASSE ABSTRAITE... 5 1 LE POLYMORPHISME 1.1 Présentation du principe à partir d un exemple Dans une hiérarchie de classe les fonctions membres de la classe de base sont redéfinies dans les classes dérivées pour spécialiser ou adapter le comportement de classe de basse. Voir exemple Modèle 1 avec la fonction membre affiche(). Les classes dérivées sont des sortes de classe de Base. Le polymorphisme permet : de définir (prototyper) une référence, un pointeur du type de la classe de base, d initialiser la référence ou le pointeur avec une instance de classe dérivée, à l exécution du programme, de bénéficier du comportement de la classe dérivée. Modèle 1 Derive1 - d1 : int = 20 Base - b : int = 1 Derive2 - d2 : int = 10 1 Base *ptr ; ptr = new Derive1 ; L instance pointée par ptr doit se comporter (utiliser les fonctions membres) de la classe Derive1 et non celles de la classe de Base. David Saint-Mellion page 1/5
2 ecran( Base *instance){ instance->affiche() ; ; Derive1 objd1 ; Derive2 objd2 ; Base objb ; ecran( &objd1) ; // ecran() doit utiliser la fonction de la classe Derive1 ecran( &objd2) ; // ecran() doit utiliser la fonction de la classe Derive2 ecran( &objb) ; // ecran() doit utiliser la fonction de la classe Base La fonction ecran() doit utiliser les fonctions membres de la classe passée en paramètre et non celles du type d objet pointé (Base) défini dans le prototype de la fonction. 1.2 Intérêt Le polymorphisme permet de déclarer une seule variable de type pointeur (ou de référence) pour un ensemble d objets issus d une hiérarchie de classes et de choisir à l exécution du programme le type d objet à créer. 2 LES LIMITES DE L HERITAGE L'héritage permet de réutiliser le code écrit pour la classe de base dans les autres classes de la hiérarchie des classes. Les méthodes qui sont utilisées (appelée dans le programme) sont définies à la compilation et non dynamiquement à l exécution du programme. class Base { int b; ; class Derive2 : public Base { int d2; ; class Derive1 : public Base { int d1; ; Base::affiche(){ cout << "Base" << endl; Derive1::affiche(){ cout << "Derive1" << endl; Derive2::affiche(){ cout << "Derive2" << endl; de programme utilisant les instances des classes dérivées void ecran(base *instance){ La fonction ecran() reçoit comme argument un instance-> pointeur du type classe Base. int main(void){ return 0; A l exécution, si on lui passe en paramètre un objet d une des classes dérivées. C est la fonction affiche() de la classe Base qui est exécutée et non celle de la classe à laquelle appartient l instance. Nous n avons pas un comportement polymorphe. La fonction affiche() exécutée n est pas celle de l instance passée en paramètre.. David Saint-Mellion page 2/5
3 IMPLEMENTATION DU POLYMORPHISME EN LANGAGE C++ Le polymorphisme permet d appeler dynamiquement (à l exécution du programme) les différentes fonctions membres dans une hiérarchie de classes. Dans l exemple, le polymorphisme permet d utiliser la fonction affiche() correspondant à la classe dont l instance est passée en paramètre. En langage C++, le polymorphisme est mis en œuvre par l'utilisation des fonctions virtuelles. 3.1 Les fonctions virtuelles Pour que la fonction ecran() appelle la fonction affiche() correspondant à la classe dont l'instance est passée en paramètre, il faut stéréotyper la méthode affiche() comme étant virtuelle. Dans le prototype de la fonction, en langage C++, il faut utiliser le mot clef «virtual» virtual affiche() ; Base Le programme à l exécution détermine la bonne méthode à appeler. Le compilateur rencontrant une méthode virtuelle sait qu'il faut attendre l'exécution pour déterminer la fonction à utiliser. - b : int = 1 Remarque : La déclaration virtuelle peut se limiter à la classe de base. Dans un but de clarté, il est préférable de déclarer virtuelles les fonctions dans les classes dérivées. (Facilite l identification des fonctions virtuelles dans une hiérarchie de classes) Derive2 - d2 : int = 20 Derive1 - d1 : int = 10 3.2 de programme class Derive2 : public Base{ virtual int d2; ; class Derive1 : public Base{ virtual int d1; ; Utilisation des classes Base objb; ecran(&objb ); class Base{ int b; virtual ; Base::affiche(){ cout << "Base" << endl; Derive1::affiche(){ cout << "Derive1" << endl; Derive2::affiche(){ cout << "Derive2" << endl; Avec void écran(base *instance){ instance-> La fonction ecran() reçoit comme argument un pointeur du type classe Base. A l exécution, c est la fonction affiche() appartenant à l instance de la classe passée en paramètre qui est appelée et non celle du prototype de l argument (Base) Résultat La fonction affiche() exécutée est celle de l instance passée en paramètre à la fonction ecran(). Le comportement est polymorphe. David Saint-Mellion page 3/5
4 TYPAGE DU DESTRUCTEUR Soit la hiérarchie de 3 classes Base, Derive1 et Derive2. Le corps des destructeurs est le suivant : Base::~Base() { cout << "~Base" << endl; Derive1::~Derive1() { cout << "~Derive1" << endl; Derive2::~Derive2() { cout << "~Derive2" << endl; 4.1 Mise en œuvre du destructeur par défaut Les constructeurs sont typés non virtuel : ~Base(), ~Derive1(), ~Derive2(). de mise en œuvre Base *ptr1,*ptr2; ptr1 = new Derive1; ptr2 = new Derive2; Résultat delete ptr1; delete ptr2; Seul le destructeur de la classe de base est utilisé (Voir résultat ci-dessus fenêtre Dos). Toute la mémoire n est pas libérée à l issue de l exécution du programme. 4.2 Le destructeur typé virtuel Lorsque l'on utilise une méthode virtuelle dans une classe, le destructeur doit être virtuel. virtual ~Base() ; 4.2.1 1 : Programme mettant en œuvre des instances de classes dérivée à partir d un pointeur typé classe de base Base *ptr1,*ptr2; ptr1 = new Derive1; ptr2 = new Derive2; delete ptr1; delete ptr2; Résultat avec destructeur virtuel 4.2.2 2 : Programme passant en paramètre à une fonction une instance d une classe dérivée dont l argument est typé classe de base void ecran(base *instance){ instance-> int main(void){ Résultat avec destructeur virtuel return 0; 4.3 Remarques Un constructeur ne peut pas être déclaré comme virtuel. (Une méthode statique non plus) Un constructeur ne peut pas appeler une fonction virtuelle. Lors de l'héritage, le statut de l'accessibilité de la méthode virtuelle (public, protégé ou privé) est conservé dans toutes les classes dérivées. Le statut de la classe de base prime. David Saint-Mellion page 4/5
5 IMPLANTATION DES FONCTIONS VIRTUELLES EN MEMOIRE L appel des fonctions virtuelles a lieu à l exécution du programme et non à la compilation. Une ligature dynamique est effectuée. Afin de choisir la bonne méthode à appeler, une table est implantée en mémoire attachée à chaque classe. Elle contient les adresses des fonctions virtuelles utilisables. Sur l exemple ci contre, apparaît 1 pointeur _vtptr donnée membre de chaque l objet. Il fait référence à la table permettant l appeler les fonctions virtuelles. La table se situe pour l instance de : Derive1 à l adresse 0x00426050 (instance objd1) Derive2 à l adresse 0x00426074 (instance objd1) Les adresses des fonctions virtuelles se situent, pour l instance objd2, en : 0x0041041 pour le destructeur. Cette adresse est contenue par _vtptr[0] 0x004104b pour affiche(). Cette adresse est contenue par _vtptr[1] La donnée membre pointeur _vtptr est attachée la partie incluse de la classe de base (Base). Une instance d une classe disposant de fonctions virtuelles est augmentée de la taille d un pointeur. sizeof(objd2) 12 octets ( d2 int - 4 octets b int - 4 octets, le pointeur _vtptr 4 octets) 6 CLASSE ABSTRAITE 6.1 Définition et Méthode virtuelle pure Une classe est dite abstraite si elle contient au moins une méthode virtuelle pure. Une méthode virtuelle pure se déclare en ajoutant un «= 0» à la fin de sa déclaration. class Base { Contrairement à une méthode virtuelle "normale", une méthode virtual affiche() = 0; virtuelle pure n a pas de corps. ; 6.2 Contraintes d utilisation d une classe Abstraite Une classe abstraite ne peut pas être utilisée pour créer des instances d'une classe abstraite. Base objb; // ERREUR Les pointeurs et les références sur une classe abstraite sont parfaitement légitimes et justifiés. Une classe abstraite s utilise à partir d'une référence ou d'un pointeur. ecran(base *obj){ // VALIDE obj-> Une classe dérivée qui ne redéfinit pas une méthode virtuelle pure est, elle-même, abstraite. David Saint-Mellion page 5/5