(Vient du grec et signifie « Peut prendre plusieurs formes ») Chapitre VI Polymorphisme (Vient du grec et signifie « Peut prendre plusieurs formes ») Permet à une variable déclarée comme pointeur vers un type de base de contenir une valeur d’un type dérivé. Exemple : Imaginons un jeu d’échec comportant des objets fou, roi, tour, … La méthode « mouvement() » pourra, grâce au polymorphisme, effectuer le mouvement approprié d’une pièce grâce au type de pièce qui lui sera associé.
Pointeurs d’une classe de base vs pointeurs de classes dérivées Héritage et pointeur de base On peut définir un pointeur vers une classe de base pour stocker l’adresse d’objets dérivés. Si les classes B et et C dérivent de A, on peut définir un pointeur vers la classe A pour stocker soit l’adresse d’un objet de type B soit l’adresse d’un objet de type C. Ex.: Polygone_2D * P; P = new Polygone_2D; delete P; P = new Polygone_2D_convexe; Cette affectation est possible uniquement parce que la classe Polygone_2D_convexe dérive de la classe Polygone_2D. Chapitre VI - Polymorphisme
Pointeurs d’une classe de base vs pointeurs de classes dérivées Le sens d’affectation est primordial. C++ ne permet pas de stocker l’adresse d’un objet de type Polygone_2D dans un pointeur Polygone_2D_convexe. Attention Il est possible de déclarer une seule variable de type pointeur puis choisir au moment de l’exécution du programme le type d’objet à créer. Intérêt Pour gérer des ensembles d’objets, il suffit de définir un tableau de pointeurs vers la classe de base pour stocker l’adresse de n’importe quel objet dérivé. Intérêt On peut définir un tableau de pointeurs vers des polygones, des polygones convexes et des triangles. Polygone_2D * Tableau[20]; Ex.: Collection hétérogène On peut effectuer un forçage de type explicite pour convertir un pointeur de classe de base en un pointeur de classe dérivée (voir exemples). Chapitre VI - Polymorphisme
Chapitre VI - Polymorphisme Substitution des fonctions membres d’une classe de base dans une classe dérivée La substitution est faite en fournissant une nouvelle version de cette fonction avec la même signature. Une signature différente => une surcharge de fonction et non une substitution de fonction. Note : En mentionnant cette fonction par son nom dans la classe dérivée, la version de la classe dérivée est choisie automatiquement. On peut utiliser l’opérateur de résolution de portée (::) pour accéder à la version de la classe de base à partir de la classe dérivée. Par ex., il arrive souvent que la version de la classe dérivée appelle la version de la classe de base en plus d’effectuer certains travaux additionnels. Chapitre VI - Polymorphisme
Forçage de types explicites et substitution des fonctions membres #include <iostream.h> typedef enum {insecte = 0, vent, animal, eau, humain} mode_de_pollinisation; typedef enum {guepe, coccinelle, mouche, libellule, cigale, abeille, fourmi, sauterelle, patineur} type_insecte; class Pollinisation { protected : mode_de_pollinisation Mode; public : Pollinisation(mode_de_pollinisation M); mode_de_pollinisation Acces_mode(); void Clone(Pollinisation P); }; Chapitre VI - Polymorphisme
Forçage de types explicites et substitution des fonctions membres Pollinisation::Pollinisation(mode_de_pollinisation M) { Mode = M; cout << "Constructeur de la classe de base: " << Mode << endl; } mode_de_pollinisation Pollinisation::Acces_mode() return Mode; void Pollinisation::Clone(Pollinisation P) Mode = P.Acces_mode(); cout << "Mode : " << Mode << endl; Chapitre VI - Polymorphisme
Forçage de types explicites et substitution des fonctions membres class Pollinisation_par_insecte : public Pollinisation { private : type_insecte Nom_d_insecte; public : Pollinisation_par_insecte(type_insecte Nom); type_insecte Acces_nom(); void Clone(Pollinisation_par_insecte P); }; Pollinisation_par_insecte::Pollinisation_par_insecte(type_insecte Nom) :Pollinisation(insecte) Nom_d_insecte = Nom; cout << "Constructeur de la classe derivee: " << Nom_d_insecte << endl; } type_insecte Pollinisation_par_insecte::Acces_nom() { return Nom_d_insecte; } Chapitre VI - Polymorphisme
Forçage de types explicites et substitution des fonctions membres void Pollinisation_par_insecte::Clone(Pollinisation_par_insecte P) { Nom_d_insecte = P.Acces_nom(); cout << "Nom : " << Nom_d_insecte << endl; Pollinisation::Clone(P); } void main() Pollinisation P(vent); Pollinisation Q(eau); Q.Clone(P); Pollinisation_par_insecte R(abeille); Pollinisation_par_insecte S(cigale); R.Clone(S); Chapitre VI - Polymorphisme
Forçage de types explicites et substitution des fonctions membres P.Clone(S); Pollinisation_par_insecte * T = new Pollinisation_par_insecte(fourmi); ((Pollinisation *) T) -> Clone(Q); ((Pollinisation *) T) -> Clone(S); // T -> Clone(Q); Conversion de Q impossible. T -> Clone(S); Pollinisation * U = new Pollinisation(insecte); // ((Pollinisation_par_insecte *) U) -> Clone(Q); Conversion de Q impossible. ((Pollinisation_par_insecte *) U) -> Clone(S); U -> Clone(Q); U -> Clone(S); } Chapitre VI - Polymorphisme
Forçage de types explicites et substitution des fonctions membres Constructeur de la classe de base: 1 Mode : 0 Constructeur de la classe de base: 3 Constructeur de la classe de base: 0 Mode : 1 Nom : 4 Constructeur de la classe de base: 0 Mode : 0 Constructeur de la classe dérivée: 5 Mode : 1 Constructeur de la classe dérivée: 4 Nom : 4 Mode : 0 Constructeur de la classe de base: 0 Constructeur de la classe dérivée: 6 Mode : 1 Chapitre VI - Polymorphisme
Exemple : classe Pile (Pile.h) class element { }; class pile /* Spécification fonctionnelle de la classe " pile ". Éléments : Chaque sommet de la pile renferme l'adresse d'un élément, non pas l'élément lui-même où le type de chaque élément est une classe dérivée de la classe de base "element". Structure : Les éléments sont reliés entre eux permettant de déterminer l'ordre d'arrivée des éléments dans la pile. */ Chapitre VI - Polymorphisme
Exemple : classe Pile (Pile.h) protected: struct sommet_pile { element * pElement; struct sommet_pile *suivant; }; struct sommet_pile * pPile; Chapitre VI - Polymorphisme
Exemple : classe Pile (Pile.h) public: void Creer_pile(); /* Permet de créer une pile vide. Pré - Nil. Post - La pile existe et est vide. */ void Inserer(element * pElement); /* Insérer l'adresse d'un élément dans la pile. Pré - La pile a déjà été créée et n'est pas pleine. Post - La pile renferme pElement et l'interprète comme étant l'adresse de l'élément le plus récent inséré dans la pile */ Chapitre VI - Polymorphisme
Exemple : classe Pile (Pile.h) element * Enlever(); /* Enlever un élément de la pile. Pré - La pile a déjà été créée et n'est pas vide. Post - L'adresse de l'élément le plus récent inséré dans la pile est retourné; cet élément ne fait plus partie de la pile. */ bool Pile_vide(); /* Vérifier si la pile est vide ou non. Pré - La pile a déjà été créée. Post - Si la pile ne possède aucun élément alors retourner true sinon retourner false. */ Chapitre VI - Polymorphisme
Exemple : classe Pile (Pile.h) bool Pile_pleine(); /* Vérifier si la pile est pleine ou non. Pré - La pile a déjà été créée. Post - Si la pile a atteint sa capacité maximale alors retourner vrai sinon retourner faux. */ void Vider_pile(); /* Vider la pile. Post - La pile est vide. */ }; Chapitre VI - Polymorphisme
Exemple: classe Pile (Pile.cpp) #include <iostream.h> #include "Pile.h" void pile::Creer_pile() { pPile = NULL; } void pile::Inserer(element * pElement) struct sommet_pile *pe = new sommet_pile; (*pe).pElement = pElement; (*pe).suivant = pPile; pPile = pe; Chapitre VI - Polymorphisme
Exemple : classe Pile (Pile.cpp) element * pile::Enlever() { element * pElement; struct sommet_pile *pe = NULL; pElement = (*pPile).pElement; pe = pPile; pPile = (*pPile).suivant; delete(pe); return pElement; } Chapitre VI - Polymorphisme
Exemple : classe Pile (Pile.cpp) bool pile::Pile_vide() { if (pPile == NULL ) return true; else return false; } bool pile::Pile_pleine() /* Il n'y a aucune façon de tester si la liste chaînée est pleine i.e. s'il existe encore de l'espace disponible pour un autre sommet "sommet_pile". */ return false; Chapitre VI - Polymorphisme
Exemple : classe Pile (Pile.cpp) void pile::Vider_pile() { element * pElement; while (Pile_vide() == false) pElement = Enlever(); } Chapitre VI - Polymorphisme
Utilisation de plusieurs piles #include <iostream.h> #include "pile.h" class entier: public element { public: int e; }; class reel: public element float r; class caractere: public element { public: char c; }; Chapitre VI - Polymorphisme
Utilisation de plusieurs piles void main() { entier i, j, k; reel a, b; caractere u; i.e = 0; j.e = 1; k.e = 2; a.r = 0.0f; b.r = 0.1f; u.c = 'a'; pile Pile_entiers, Pile_reels, Pile_caracteres; Pile_entiers.Creer_pile(); Pile_reels.Creer_pile(); Pile_caracteres.Creer_pile(); Chapitre VI - Polymorphisme
Utilisation de plusieurs piles Pile_entiers.Inserer(&i); Pile_entiers.Inserer(&j); Pile_entiers.Inserer(&k); Pile_reels.Inserer(&a); Pile_reels.Inserer(&b); Pile_caracteres.Inserer(&u); cout << (* (entier *) Pile_entiers.Enlever()).e; cout << (* (reel *) Pile_reels.Enlever()).r; cout << (* (caractere *) Pile_caracteres.Enlever()).c; Chapitre VI - Polymorphisme
Utilisation de plusieurs piles if ((Pile_entiers.Pile_vide() == true) && (Pile_reels.Pile_vide() == true) && (Pile_caracteres.Pile_vide() == true)) cout << "Les 3 piles sont vides."; } Chapitre VI - Polymorphisme
Chapitre VI - Polymorphisme Une pile de dossiers #include <iostream.h> #include <string.h> #include "pile.h" class dossier: public element { protected: int code; char description[20+1]; float priorite; public: dossier(int c, char * d ="", float p = 0.0f); int Acces_Code(); char * Acces_Description(); float Acces_Priorite(); }; Chapitre VI - Polymorphisme
Chapitre VI - Polymorphisme Une pile de dossiers dossier::dossier(int c, char * d, float p) { code = c; priorite = p; strcpy(description, d); } int dossier::Acces_Code() return code; Chapitre VI - Polymorphisme
Chapitre VI - Polymorphisme Une pile de dossiers char * dossier::Acces_Description() { return description; } float dossier::Acces_Priorite() return priorite; Chapitre VI - Polymorphisme
Chapitre VI - Polymorphisme Une pile de dossiers void main() { dossier c(36); dossier b(12, "description I : ", 0.25); dossier a(24, "description II : "); pile Pile_dossiers; Pile_dossiers.Creer_pile(); Pile_dossiers.Inserer(&a); Pile_dossiers.Inserer(&b); Pile_dossiers.Inserer(&c); Chapitre VI - Polymorphisme
Chapitre VI - Polymorphisme Une pile de dossiers cout << (* (dossier *) Pile_dossiers.Enlever()).Acces_Code(); cout << (* (dossier *) Pile_dossiers.Enlever()).Acces_Description(); cout << (* (dossier *) Pile_dossiers.Enlever()).Acces_Priorite(); if (Pile_dossiers.Pile_vide() == true) cout << "La pile est vide."; } FIN Chapitre VI - Polymorphisme
Chapitre VI - Polymorphisme Une forêt, pas un arbre Certains langages de programmation orientés objet (comme par ex. Smalltalk et Java) placent tous les objets dans une unique grande hiérarchie d’héritage : Tous les objets dans ces langages descendent d’une unique classe de base nommée le plus souvent Object. Avantages : Un pointeur vers un objet de cette classe peut renfermer tout type de valeur. Tout comportement défini dans la classe Object est commun à toutes les classes. Le langage C++ ne procède pas ainsi; il permet aux programmeurs de créer une forêt de nombreux petits arbres d’héritage. Chapitre VI - Polymorphisme
Chapitre VI - Polymorphisme Pourquoi les notions d’héritage et de pointeurs de base ne peuvent s’appliquer à la construction de la classe Vecteur pour éliminer la surcharge de fonctions? Technique associée au polymorphisme: la redéfinition des fonctions membres des classes. INTÉRÊT Le polymorphisme permet à des objets de classes différentes liées par héritage de répondre de façon distincte à un appel de fonction membre. Chapitre VI - Polymorphisme
Exemple permettant d’introduire le polymorphisme Tiré de [Dupin, 99; pp. 247-253] #include <iostream.h> #include <string.h> class materiel { protected: char Reference[20+1]; char Marque[20+1]; public: Materiel(char *r, char *m); void Affiche(); }; class Micro : public Materiel { protected: char Processeur[20+1]; int Disque; public: Micro(char *r, char *m, char *p, int d); void Affiche(); }; Chapitre VI - Polymorphisme
Exemple permettant d’introduire le polymorphisme Micro :: Micro(char *r, char *m, char *p, int d) :Materiel(r,m) { strcpy(Processeur, p); Disque = d; } void Micro::Affiche() cout << "Ref : " << Reference; cout << "Marque : " << Marque; cout << "Proc : " << Processeur; cout << "Disque : " << Disque; cout << "\n"; Materiel::Materiel(char *r, char *m) { strcpy(Reference, r); strcpy(Marque, m); } void Materiel::Affiche() cout << "Ref : " << Reference; cout << "Marque : " << Marque; cout << "\n"; Chapitre VI - Polymorphisme
Exemple permettant d’introduire le polymorphisme int main() { Materiel *pMat; pMat = new Materiel("X01", "XX"); pMat -> Affiche(); delete pMat; pMat = new Micro("X16", "PH", "DX4-100", 200); return 0; } Ref : X01 Marque : XX Ref : X16 Marque : PH Chapitre VI - Polymorphisme
Exemple permettant d’introduire le polymorphisme Erreur Les 2 classes possèdent une fonction Affiche(). Le résultat affiché est invalide car c’est la méthode Affiche() de Materiel qui a été utilisée chaque fois. Pourquoi? Le compilateur a tenu compte du type de pointeur (Materiel *) et non pas du type d’objet pointé par pMat. Comportement non polymorphique Il arrive qu’une fonction membre non virtual soit définie dans une classe de base et redéfinie dans une classe dérivée. Si on appelle cette fonction par un pointeur de classe de base vers l’objet de classe dérivée, la version de classe de base est alors utilisée. Si on appelle cette fonction au moyen d’un pointeur de classe dérivée, la version utilisée est alors celle de classe dérivée.
Exemple permettant d’introduire le polymorphisme Solution Il faudrait que le compilateur appelle la fonction Affiche() de Micro (resp. Materiel) quand pMat contient l’adresse d’un objet de type Micro (resp. Materiel). Il faut indiquer au compilateur que la fonction Affiche() est une fonction polymorphe, i.e. en C++, une fonction virtuelle. Il suffit de faire précéder son prototype par le mot clé virtual. Il n’est pas nécessaire d’utiliser ce mot clé devant le corps de la fonction. Dès qu’une fonction est déclarée virtual, elle reste virtuelle à tous les niveaux de la hiérarchie d’héritage, même si elle n’est pas déclarée virtual lorsqu’elle est remplacée par une autre classe. Ceci dit, il est préférable de déclarer explicitement ces fonctions comme virtual à chaque niveau hiérarchique, pour favoriser la clarté du programme.
Déclaration d’une fonction membre polymorphe (virtuelle) Exemple précédent: class Micro : public Materiel { protected: char Processeur[20+1]; int Disque; public: Micro(char *r, char *m, char *p, int d); virtual void Affiche(); }; class materiel { protected: char Reference[20+1]; char Marque[20+1]; public: Materiel(char *r, char *m); virtual void Affiche(); }; Chapitre VI - Polymorphisme
Déclaration d’une fonction membre polymorphe (virtuelle) Ref : X01 Marque : XX Ref : X16 Marque : PH Processeur : DX4-100 Disque : 200 Une fonction polymorphe est une méthode qui est appelée en fonction du type d’objet et non pas en fonction du type de pointeur utilisé. Nous n’avons pas besoin d’utiliser une instruction switch qui exécuterait une action appropriée au type de chaque objet traité. Grâce au polymorphisme, 2 classes peuvent réagir différemment au même appel de méthode. On peut donc constituer des hiérarchies de classes qui partagent une trame commune et qui se différencient les unes des autres.
Déclaration d’une fonction membre polymorphe (virtuelle) Lorsque vous appelez une fonction virtuelle pour un objet, le C++ cherche cette méthode dans la classe correspondante. Si cette fonction n’existe pas dans la classe concernée, le C++ remonte la hiérarchie des classes jusqu’à ce qu’il trouve la fonction appelée. La résolution des appels de fonctions virtuelles a lieu à l’exécution des programmes car ce n’est qu’à cet instant, que le type d’objet pointé sera connu par C++. Toutefois, lorsque nous appelons une fonction virtual en référençant un objet spécifique par son nom, comme dans objet.Affiche(), la référence est résolue lors de la compilation et la fonction virtual appelée est celle définie pour la classe de cet objet particulier ou héritée par elle. Fonctions non virtuelles : le choix de la méthode se fait au moment de la compilation du programme. Chapitre VI - Polymorphisme
Exemple : Ajout d ’une nouvelle fonction à la classe Polygone_2D Ajout de la fonction Point_interieur_Polygone_2D Chapitre VI - Polymorphisme
Exemple : Ajout d’une nouvelle fonction à la classe Polygone_2D Polygone quelconque Polygone Convexe Point intérieur Chapitre VI - Polymorphisme
Exemple : Ajout d’une nouvelle fonction à la classe Polygone_2D Fichier Polygone_2D.h virtual void Point_interieur_Polygone_2D(float *x, float *y); /* Permet de calculer un point intérieur au polygone 2D quelconque. Pré - Le polygone 2D a déjà été créé et le nombre de sommets est >= 3. Post - Retourne en paramètres les coordonnées réelles d'un point intérieur au polygone 2D. */ Chapitre VI - Polymorphisme
Exemple : Ajout d’une nouvelle fonction à la classe Polygone_2D Fichier Polygone_2D_convexe.h virtual void Point_interieur_Polygone_2D(float *x, float *y); /* Permet de calculer un point intérieur au polygone 2D convexe. Pré - Le polygone 2D a déjà été créé et le nombre de sommets est >= 3. Post - Retourne en paramètres les coordonnées réelles d'un point intérieur au polygone 2D. */ Chapitre VI - Polymorphisme
Exemple : Ajout d’une nouvelle fonction à la classe Polygone_2D Fichier Triangle_2D.h Aucun ajout. Fichier Polygone_2D.cpp void Polygone_2D::Point_interieur_Polygone_2D(float *x, float *y) { /***************************************************** */ /* à développer */ /******************************************************/ *x = 0.0; *y = 0.0; } Chapitre VI - Polymorphisme
Exemple : Ajout d’une nouvelle fonction à la classe Polygone_2D Fichier Polygone_2D_convexe.cpp void Polygone_2D_convexe:: Point_interieur_Polygone_2D(float *x, float *y) { float u, v; Point_visible_Polygone_2D_convexe(&u, &v); *x = u; *y = v; } Fichier Triangle_2D.cpp Aucun ajout. Chapitre VI - Polymorphisme
Exemple : Ajout d’une nouvelle fonction à la classe Polygone_2D Fichier Application.cpp #include <iostream.h> #include "Triangle_2D.h" void main() { float a, b; Polygone_2D P; Triangle_2D Q; P.Ajouter_sommet(1, 1); Q.Ajouter_sommet(1, 1); P.Ajouter_sommet(1, 3); Q.Ajouter_sommet(1, 3); P.Ajouter_sommet(4, 3); Q.Ajouter_sommet(4, 3); Chapitre VI - Polymorphisme
Exemple : Ajout d’une nouvelle fonction à la classe Polygone_2D Fichier Application.cpp(suite) if (Q.Acces_statut() == triangle) cout << " Ceci est un triangle."; cout << "Nombre de sommets : " << Q.Nombre_de_sommets(); cout << "Aire du triangle 2D : " << Q.Aire_Polygone_2D(); Q.Point_visible_Polygone_2D_convexe(&a, &b); cout << "Point visible : " << a << " , " << b; Q.Point_interieur_Polygone_2D(&a, &b); cout << "Point interieur de Q: " << a << " , " << b; P.Point_interieur_Polygone_2D(&a, &b); cout << "Point interieur de P: " << a << " , " << b; Chapitre VI - Polymorphisme
Exemple : Ajout d’une nouvelle fonction à la classe Polygone_2D Fichier Application.cpp (suite) P.Detruire_Polygone_2D(); Q.Detruire_Polygone_2D(); cout << "Nombre de sommets de Q: " << Q.Nombre_de_sommets() << "Nombre de sommets de P: " << P.Nombre_de_sommets(); } FIN Chapitre VI - Polymorphisme
Polymorphisme, pour quoi faire? Dès que vous souhaitez modifier le comportement d’une classe de base ou la spécialiser. 3 raisons pour redéfinir une fonction virtuelle dans une classe dérivée: pour la substituer au traitement standard préciser dans cette fonction le nouveau traitement. pour compléter le traitement standard appeler la méthode de la classe de base dans votre fonction ajouter d’autres traitements. pour annuler le traitement standard définir la fonction virtuelle dans votre classe ne rien insérer à l’intérieur du corps de la méthode. Chapitre VI - Polymorphisme
Polymorphisme, pour quoi faire? Rien ne nous oblige à redéfinir une fonction virtuelle dans une classe dérivée si le traitement réalisé par la classe de base vous convient. Généralisation de l’appel des fonctions polymorphes Nous avons appelé jusqu’à maintenant les fonctions polymorphes directement en désignant l’objet traité. Qu’arrive-t-il lorsque cette fonction polymorphe est utilisée par une autre méthode de la classe ? Chapitre VI - Polymorphisme
Généralisation de l’appel des fonctions polymorphes Tiré de [Dupin, 99; pp. 258-260] #include <iostream.h> #include <string.h> class materiel { protected: char Reference[20+1]; char Marque[20+1]; public: Materiel(char *r, char *m); virtual void Affiche(); void AfficheTitre(); }; class Micro : public Materiel { protected: char Processeur[20+1]; int Disque; public: Micro(char *r, char *m, char *p, int d); virtual void Affiche(); }; Ajout d ’une nouvelle méthode Chapitre VI - Polymorphisme
Généralisation de l’appel des fonctions polymorphes Materiel::Materiel(char *r, char *m) { strcpy(Reference, r); strcpy(Marque, m); } void Materiel::Affiche() cout << "Ref : " << Reference; cout << "Marque : " << Marque; cout << "\n"; void Materiel::AfficheTitre() { cout << "DEBUT\n "; Affiche(); cout << "FIN\n"; } Micro :: Micro(char *r, char *m, char *p, int d) :Materiel(r,m) strcpy(Processeur, p); Disque = d; Chapitre VI - Polymorphisme
Généralisation de l’appel des fonctions polymorphes void Micro::Affiche() { cout << "Ref : " << Reference; cout << "Marque : " << Marque; cout << "Proc : " << Processeur; cout << "Disque : " << Disque; cout << "\n"; } DEBUT Ref : X16 Marque : PH Processeur : DX4-100 Disque : 200 FIN void main() { Micro *pMic = NULL; pMic = new Micro("X16", "PH", "DX4-100", 200); pMic -> AfficheTitre(); delete pMic; } Chapitre VI - Polymorphisme
Généralisation de l’appel des fonctions polymorphes L’exécution de la fonction AfficheTitre() entraîne l’appel à une fonction Affiche(), laquelle? C++ exécutera la méthode virtuelle en fonction du type d’objet, quel que soit l’endroit d’où provient cet appel i.e. la fonction Affiche() de Micro. Avantage On peut modifier un traitement standard sans y toucher. Les fonctions virtuelles ne peuvent pas être utilisées dans le corps des constructeurs. Un constructeur appellera toujours la fonction définie dans sa classe ou à défaut celle d’une classe de base, mais jamais la fonction virtuelle d’une classe dérivée. Note : Les constructeurs ne peuvent pas être virtuels. Chapitre VI - Polymorphisme
Généralisation de l’appel des fonctions polymorphes Le corps du constructeur de la classe de base s’exécute avant celui de la classe dérivée. Pourquoi? Ainsi, au moment de l’exécution du constructeur de la classe de base, l’initialisation à réaliser par le constructeur de la classe dérivée n’est pas effectuée. Un destructeur ne peut pas appeler une fonction virtuelle d’une classe dérivée. Le destructeur de la classe de base est appelé après celui de la classe dérivée. Pourquoi? Chapitre VI - Polymorphisme
Appel de la fonction membre d’une classe de base Au lieu de substituer au traitement standard, dans certains cas, il est préférable de compléter le traitement de la classe de base plutôt que de le remplacer. Il s’agit d’appeler la fonction virtuelle d’origine à partir de la nouvelle méthode. La fonction Affiche() de Micro utilise la fonction Affiche() de la classe Materiel. Ex. void Micro::Affiche() { Materiel::Affiche(); cout << " Processeur : " << Processeur; cout << " Disque : " << Disque; cout << "\n "; } Réutilisation de code Appel de la fonction de la classe de base Chapitre VI - Polymorphisme
Appel de la fonction membre d’une classe de base Si la fonction Affiche() de Materiel est modifiée, les classes dérivées en bénéficieront. Avantage Le nom complet de la fonction Affiche() de la classe Materiel doit être utilisé pour éviter un appel récursif. Chapitre VI - Polymorphisme
Polymorphisme et définition d’un destructeur Si un objet est détruit explicitement par l’application de l’opérateur delete sur un pointeur de classe de base vers l’objet, la fonction de destructeur de classe de base est appelée sur l’objet, indépendamment du type d’objet pointé par le pointeur de la classe de base. Lorsque vous définissez un destructeur de la classe de base, vous devez donc le déclarer en tant que fonction virtuelle. Autrement, la mémoire ne sera pas complètement libérée. Cela rend automatiquement virtuels les destructeurs de toutes les classes dérivées. Dès lors, si un objet de la hiérarchie est détruit explicitement avec delete sur un pointeur de classe de base vers l’objet de classe dérivée, le destructeur de la classe appropriée est appelé. Chapitre VI - Polymorphisme
Polymorphisme et définition d’un destructeur Ex. : Polygone_2D * P; P = new Polygone_2D_convexe; delete P; Si la classe Polygone_2D_convexe contenait un destructeur, et qu’il n’est pas virtuel, C++ utilisera le type de pointeur et non pas le type d’objet et détruira seulement la partie Polygone_2D de cet objet. Rappel Lorsqu’un objet de la classe dérivée est détruit, la partie classe de base de cet objet est également détruite, car le destructeur de classe de base s’exécute automatiquement après le destructeur de classe dérivée. En pratique Si une classe possède des fonctions virtuelles, fournissez-lui un destructeur virtuel même si ce dernier n’est pas obligatoire pour cette classe. Les classes dérivées de cette classe peuvent en effet contenir des destructeurs qui doivent être appelés correctement.
Classes abstraites et fonctions virtuelles pures La plupart du temps, lorsqu’on crée une classe, c’est dans le but de créer des instances de cette classe. Ce n’est pas toujours le cas … Une classe abstraite est une classe qui ne peut être instanciée, i.e. on ne peut pas créer d’objet directement à partir de cette classe, mais on le peut grâce au mécanisme de l’héritage. Même si nous ne pouvons pas instancier des objets de classes de base abstraites, nous pouvons quand même déclarer des pointeurs et des références vers ces classes pour manipuler des objets de classes dérivées de façon polymorphique lorsque ces objets sont instanciés à partir de classes concrètes (non abstraites). On peut se servir d’une telle classe pour regrouper des concepts sous une classe théorique. On peut aussi vouloir imposer une interface générale que toutes les classes héritières devront implanter. Une classe devient abstraite si elle possède au moins une fonction virtuelle pure. Chapitre VI - Polymorphisme
Classes abstraites et fonctions virtuelles pures Une fonction virtuelle pure est une fonction virtuelle pour laquelle le prototype est suivi de l’expression = 0. virtual void Affiche() = 0; Ex.: 2 incidences: Une classe qui contient la définition d’une fonction virtuelle pure devient une classe abstraite. (i) La redéfinition des fonctions virtuelles pures est obligatoire dans toutes les classes dérivées. Autrement, les classes dérivées deviennent également abstraites. (ii) Oblige les classes dérivées à redéfinir des fonctions membres de la classe de base sous peine de devenir elles-mêmes abstraites. Avantage: De cette manière, les classes dérivées n’utiliseront jamais la méthode de la classe de base. Chapitre VI - Polymorphisme
Classes abstraites et fonctions virtuelles pures Figure_geometrique_2D « hérite de » « hérite de » Conique enum type_de_figure{polygone, conique}; class Figure_geometrique_2D //classe abstraite { protected: type_de_figure Figure; public: virtual type_de_figure Acces_type_figure()=0; }; à redéfinir dans les classes dérivées Chapitre VI - Polymorphisme
Exemple de classes abstraites #include "Point_3D.h" enum type_de_courbe_parametrique {constante_3D, segment_3D, courbe_de_Bezier, courbe_parametrique_de_degre_3, courbe_parametrique_quelconque}; /* Cette classe abstraite permet de définir et de manipuler des courbes paramétriques dans [0, 1] : courbe constante, segment 3D, courbe de Bézier et courbe de degré 3. Elle permet aussi de définir des courbes paramétriques quelconques (des courbes qui ne sont pas définies dans PGC++ et pour lesquelles, il n'est pas pertinent de le faire) dans [0, 1] en construisant des classes dérivées de cette classe abstraite. Cette classe renferme des fonctions virtuelles pures lesquelles devront être redéfinies obligatoirement dans les classes dérivées. */
Exemple de classes abstraites class Courbe_parametrique { protected : type_de_courbe_parametrique Courbe; public : Courbe_parametrique(type_de_courbe_parametrique T) { Courbe = T;} /* Permet de créer une courbe paramétrique d'un certain type. Pré - Nil. Post - Une courbe de type T est créée. */ virtual type_de_courbe_parametrique Acces_type_de_courbe_parametrique() = 0; /* Permet d'identifier le type de courbe paramétrique qui a été définie. Pré - La courbe doit avoir été définie. Post - Retourne le type de courbe paramétrique créée.*/
Exemple de classes abstraites virtual Point_3D & operator ()(float u) = 0; /* Permet d'évaluer la courbe courante à u dans l'intervalle [0, 1]. Pré - La courbe courante est créée. Post - Retourne le point sur la courbe à u. */ virtual Vecteur_3D & Tangente(float u) = 0; /* Permet de calculer la tangente unitaire à la courbe à u. Post - Retourne la tangente unitaire à la courbe à u. */ virtual float Longueur(float u1, float u2) = 0; /* Permet de calculer la longueur de la courbe dans l'intervalle [u1, u2]. Pré - La courbe courante est créée. 0 <= u1, u2 <= 1. Post - Retourne la longueur de la courbe dans l'intervalle [u1, u2]. */
Exemple de classes abstraites virtual bool Courbe_plane() = 0; /* Permet de déterminer si une courbe 3D est plane ou non. Pré - La courbe courante est créée. Post - Retourne true si la courbe est plane. False autrement. La courbe n'est pas considérée plane lorsqu'il s'agit d'un point ou d'un segment de droite. */ }; Chapitre VI - Polymorphisme
Exemple de classes abstraites #include "Courbe_parametrique.h" class Segment_3D : public Courbe_parametrique { protected : Point_3D S1; Point_3D S2; public : Segment_3D(Point_3D & P1, Point_3D & P2); /* Permet de créer un segment de droite 3D. Pré - Nil. Post - Un segment de droite 3D d'extrémités P1 et P2 est créée. */ type_de_courbe_parametrique Acces_type_de_courbe_parametrique(); /* Permet d'identifier le type de courbe parametrique définie. Pré - La courbe doit avoir été définie. Post - Retourne le type de courbe parametrique créée, soit un segment de droite. */
Exemple de classes abstraites void Acces_definition_segment_3D(Point_3D * P1, Point_3D * P2); /* Donne accès à la définition du segment 3D courant. Pré - La courbe doit avoir été définie. Post - Retourne les extrémités du segment 3D courant. */ Segment_3D & operator = (Segment_3D & S); /* Permet d'affecter S à la courbe courante. Pré - S est créée. Le segment courant est créé. Post - La courbe courante renferme maintenant la définition de S.*/ Point_3D & operator[](int i); /* Permet d'accéder ou de modifier l'extrémité i (= 1 ou 2) du segment de droite courant. Pré - Le segment de droite courant est créé et initialisé. Post - Accède ou modifie l'extrémité i (= 1 ou 2) du segment de droite courant. */
Exemple de classes abstraites Point_3D & operator ()(float u); /* Permet d'évaluer la courbe courante à u dans l'intervalle [0, 1]. Pré - La courbe courante est créée. Post - Retourne le point sur la courbe à u. */ Vecteur_3D & Tangente(float u); /* Permet de calculer la tangente au segment de droite à u. Post - Retourne le vecteur S2 - S1. */ Chapitre VI - Polymorphisme
Exemple de classes abstraites float Longueur(float u1, float u2); /* Permet de calculer la longueur du segment de droite dans l'intervalle [u1, u2]. Pré - Le segment de droite est créé. 0 <= u1, u2 <= 1. Post - Retourne la longueur du segment de droite dans l'intervalle [u1, u2]. */ bool Courbe_plane(); /* Pré - La courbe courante est créée. Post - Retourne false car la courbe n'est pas considérée plane lorsqu’il s'agit d'un point ou d'un segment de droite. */ }; Segment_3D.cpp Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme // Exemple tiré du livre "Comment programmer en C++, Deitel et Deitel, // Eyrolles, pp. 569-579. #include <iostream.h> #include <string.h> #include <iomanip.h> class Employe // Classe de base abstraite { private: char * prenom; char * nomFamille; public: Employe(const char * prenom, const char * nom); ~Employe(); const char * lecturePrenom() const; const char * lectureNomFamille() const; virtual double gains() const = 0; // Fonction virtuelle pure virtual void affichage() const; // Fonction virtuelle };
Système de paie utilisant le polymorphisme Employe::Employe(const char * premier, const char * dernier) { prenom = new char[strlen(premier) + 1]; strcpy(prenom, premier); nomFamille = new char[strlen(dernier) + 1]; strcpy(nomFamille, dernier); } Employe::~Employe() delete [] prenom; delete [] nomFamille; Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme // Renvoie un pointeur vers le prénom. Un renvoi de type const empêche // l'appelant de modifier les données private. L'appelant doit copier // la chaîne renvoyée avant que le destructeur ne supprime la mémoire // dynamique pour empêcher la formation d'un pointeur non défini. const char * Employe::lecturePrenom() const { return prenom; } // Renvoie un pointeur vers le nom de famille. Un renvoi de type const // empêche l'appelant de modifier les données private. L'appelant doit // copier la chaîne renvoyée avant que le destructeur ne supprime la // mémoire dynamique pour empêcher la formation d'un pointeur non défini. const char * Employe::lectureNomFamille() const { return nomFamille; Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme void Employe::affichage() const { cout << prenom << " " << nomFamille; } //------------------------ Fin de la classe Employe ------------------- Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme class Patron: public Employe // Classe dérivée de la classe Employe { private: double salaireHebdo; public: Patron(const char * prenom, const char * nom, double salaireHebdo = 0.0); void ajusterSalaireHebdo(double salaireHebdo); // Ajuste le salaire du patron. virtual double gains() const; // Lit la paie du patron. virtual void affichage() const; // Affiche le nom du patron. }; Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme Patron::Patron(const char * premier, const char * dernier, double salaire) : Employe(premier, dernier) { ajusterSalaireHebdo(salaire); } void Patron::ajusterSalaireHebdo(double salaire) { salaireHebdo = salaire; double Patron::gains() const { return salaireHebdo; void Patron::affichage() const { cout << "\n Patron: "; Employe::affichage(); //------------------------ Fin de la classe Patron -------------------
Système de paie utilisant le polymorphisme class EmployeCommission: public Employe // Classe dérivée de la classe Employe { private: double salaire; // salaire hebdomadaire de base double commission; // montant par article vendu int quantite; // total d'articles vendus pour la semaine public: EmployeCommission(const char * prenom, const char * nom, double salaire = 0.0, double commission = 0.0, int quantite = 0); void ajusterSalaire(double salaire); // Ajuste le salaire. void ajusterCommission(double commission); // Ajuste la commission. void ajusterQuantite(int quantite); // Ajuste le total d'articles vendus. virtual double gains() const; // Lit la paie de l'employé à commission. virtual void affichage() const; // Affiche le nom de l'employé à commission. };
Système de paie utilisant le polymorphisme EmployeCommission::EmployeCommission(const char * premier, const char * dernier, double s, double c, int q) : Employe(premier, dernier) { ajusterSalaire(s); ajusterCommission(c); ajusterQuantite(q); } void EmployeCommission::ajusterSalaire(double s) salaire = s; void EmployeCommission::ajusterCommission(double c) commission = c; Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme void EmployeCommission::ajusterQuantite(int q) { quantite = q; } double EmployeCommission::gains() const return salaire + commission * quantite; void EmployeCommission::affichage() const cout << "\n Employe a commission: "; Employe::affichage(); //--------------------- Fin de la classe EmployeCommission ---------------- Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme class EmployePiece: public Employe // Classe dérivée de la classe Employe { private: double tauxParPiece; // taux pour chaque pièce produite int quantite; // quantité produite pour la semaine public: EmployePiece(const char * prenom, const char * nom, double tauxParPiece = 0.0, int quantite = 0); void ajusterPaie(double tauxParPiece); // Ajuste le taux par pièce. void ajusterQuantite(int quantite); // Ajuste la qté produite pour la semaine. virtual double gains() const; // Lit la paie de l'employé à la pièce. virtual void affichage() const; // Affiche le nom de l'employé à la pièce. }; Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme EmployePiece::EmployePiece(const char * premier, const char * dernier, double w, int q) : Employe(premier, dernier) { ajusterPaie(w); ajusterQuantite(q); } void EmployePiece::ajusterPaie(double w) tauxParPiece = w; void EmployePiece::ajusterQuantite(int q) quantite = q; Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme double EmployePiece::gains() const { return tauxParPiece * quantite; } void EmployePiece::affichage() const cout << "\nEmploye paye a la piece: "; Employe::affichage(); //------------------------ Fin de la classe EmployePiece ------------------- Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme class EmployeHoraire: public Employe // Classe dérivée de la classe Employe { private: double tauxHoraire; // taux horaire double heures; // heures travaillées pour la semaine public: EmployeHoraire(const char * prenom, const char * nom, double tauxHoraire = 0.0, double heures = 0); void ajusterPaie(double tauxHoraire); // Ajuste le taux horaire. void ajusterHeures(double heures); // Ajuste les heures travaillées pour la semaine. virtual double gains() const; // Lit la paie de l'employé à l'heure. virtual void affichage() const; // Affiche le nom de l'employé à l'heure };
Système de paie utilisant le polymorphisme EmployeHoraire::EmployeHoraire(const char * premier, const char * dernier, double w, double h) : Employe(premier, dernier) { ajusterPaie(w); ajusterHeures(h); } void EmployeHoraire::ajusterPaie(double w) tauxHoraire = w; void EmployeHoraire::ajusterHeures(double h) heures = h; Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme double EmployeHoraire::gains() const { if (heures <= 40) return tauxHoraire * heures; else return 40 * tauxHoraire + (heures - 40) * tauxHoraire * 1.5; } void EmployeHoraire::affichage() const cout << "\n Employe horaire: "; Employe::affichage(); //------------------------ Fin de la classe EmployeHoraire ------------------- Chapitre VI - Polymorphisme
Système de paie utilisant le polymorphisme void virtuelViaPointeur( const Employe * E); void virtuelViaReference(const Employe & E); void main() { Patron b("Jean", "Soucy", 800.00); b.affichage(); cout << " a gagne $" << b.gains(); virtuelViaPointeur( &b ); virtuelViaReference( b ); EmployeCommission c("Lise", "Jobin", 200.00, 3.0, 150); c.affichage(); cout << " a gagne $" << c.gains(); virtuelViaPointeur( &c ); virtuelViaReference( c );
Système de paie utilisant le polymorphisme EmployePiece p("Benoit", "Cyr", 2.5, 200); p.affichage(); cout << " a gagne $" << p.gains(); virtuelViaPointeur( &p ); virtuelViaReference( p ); EmployeHoraire h("Karine", "Roy", 13.75, 40); h.affichage(); cout << " a gagne $" << h.gains(); virtuelViaPointeur( &h ); virtuelViaReference( h ); cout << endl; }
Système de paie utilisant le polymorphisme void virtuelViaPointeur( const Employe * classeBasePtr ) { classeBasePtr -> affichage(); cout << " a gagne $" << classeBasePtr -> gains(); } void virtuelViaReference( const Employe & classeBaseRef ) classeBaseRef.affichage(); cout << " a gagne $" << classeBaseRef.gains();
Système de paie utilisant le polymorphisme Patron: Jean Soucy a gagne $800.00 Employe a commission: Lise Jobin a gagne $650.00 Employe paye a la piece: Benoit Cyr a gagne $500.00 Employé horaire: Karine Roy a gagné $550.00 FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN FIN