Vous êtes sur la page 1sur 29

Cours C++ : Polymorphisme

1
Compatibilité entre classe de base et
classe dérivée
 En POO, on considère d’une manière générale qu’un objet d’une
classe dérivée peut remplacer un objet d’une classe de base
(l’objet de la classe dérivée possède toutes les potentialités pour
remplacer un objet de la classe de base). L’inverse n’est pas vrai.

 En C++, cette compatibilité ne s’applique que pour la dérivation


publique et se résume aux conversions implicites suivantes :
 Un objet de type dérivé dans un objet de type de base,
 Un pointeur sur un objet de type dérivé en un pointeur sur un objet de
type de base.
 Question : Pourquoi elle ne s’applique pas à la dérivation privée?

 Après une telle conversion, seules les fonctionnalités de l’objet de


type de base sont applicables.

2
Compatibilité entre classe de base et
classe dérivée
 Si B hérite de A, alors toutes les instances de B sont aussi des instances
de A, et il est donc possible de faire :
Aa ; B b; a = b;
// Propriété conservée lorsqu’on utilise des pointeurs :
A *pa; B *pb=&b; pa = pb; /** car pointer sur un B c’est avant tout
pointer sur un A **/
//Evidement , l’inverse n’est pas vrai :
A a; B b; b = a; // ERROR !
// Pareil pour les pointeurs :
A *pa=&a; B *pb; pb=pa; /** ERROR : car pointer sur un A n’est pas
pointer sur un B **/

 Conclusion :
 Traiter un type dérivé comme s’il était de son type de base est appelé
transtypage ascendant ou surtypage (upcasting).
 A l’opposé, les transtypage descendant (downcast) posent un problème
particulier car leur vérification n’est possible qu’à l’exécution.

3
L’édition de lien et le polymorphisme
 L’édition de lien est l’opération qui associe, à l’appel d’une
fonction, l’adresse du code de la fonction.

On distingue:
 La liaison statique (ou précoce): Quand une méthode à liaison statique
est appelée sur un objet, c’est la méthode correspondant à la classe de
cet objet déterminée au moment de la compilation qui est exécutée.
 La liaison dynamique (ou tardive) : Quand une méthode à liaison
dynamique est appelée sur un objet, c’est la méthode correspondant à
la classe réelle de cet objet déterminée au moment de l’exécution qui
est exécutée.

 Le polymorphisme est la capacité, pour une fonction, d’être


interprétée différemment en fonction de l’objet auquel elle
s’applique.

4
Résolution des liens
 Une instance de sous-classe B est substituable à une instance de
super-classe A.

 Que se passe-t-il lorsque B redéfinit une méthode de A ?

class A {
public: void affiche(){cout<<"A"<<endl;} };
class B: public A {
public: void affiche(){cout<<"B"<<endl;}
};
int main ()
{
A aa;
B bb;
aa = bb;
aa.affiche(); // appel affiche() de A ? ou affiche() de B ?
}

5
Résolution des liens
 En C++, c’est le type de la variable qui détermine la méthode à
exécuter.
int main ()
 Résolution statique des liens. {
A aa;
B bb;
aa = bb;
aa.affiche(); // appel affiche() de A
}

 Le choix de la fonction est conditionné par le type de aa connu au


moment de la compilation.

 Puisque aa est un A alors il utilise la méthode A::affiche().


Il serait mieux que l’appel de la méthode affiche() soit B::affiche() ?
puisque aa contient un B.

 En POO, le programme ne peut déterminer l’adresse du code avant la


phase d’exécution, un autre mécanisme est donc nécessaire quand
un message est envoyé à un objet générique.
6
Résolution dynamique des liens
 Il pourrait dans certains cas sembler plus naturel de choisir la
méthode correspondante à la nature réelle de l’instance.

 Dans ces cas, il faut permettre la résolution dynamique des liens : Le


choix de la méthode à exécuter se fait à l’exécution, en fonction
de la nature réelles des instances.

 Les langages orientés objet utilisent le concept d’association


tardive. Quand un objet reçoit un message, le code appelé n’est
pas déterminé avant l’exécution. Le compilateur s’assure que la
fonction existe et vérifie le type des arguments et de la valeur de
retour, mais il ne sait pas exactement quel est le code à exécuter.

7
Méthodes virtuelles
 En C++, on indique au compilateur qu’une méthode peut faire
l’objet d’une résolution dynamique des liens en la déclarant
comme virtuelle (mot clé virtual).
Cette déclaration doit se faire dans la classe la plus générale qui
admet cette méthode (c’est-à-dire lors du prototypage d’origine).

 Les redéfinitions éventuelles dans les sous-classes seront aussi


considérées comme virtuelles par transitivité.

8
Polymorphisme
 polymorphisme : possibilité d'utiliser le même code avec différent
types d'objets et se comporte différemment avec chacun.
 L'héritage fournit un polymorphisme à l'exécution.

 Idée : le code client peut appeler une méthode sur différents types
d'objets, et le comportement résultant sera différent.

9
Polymorphisme : Classe Employee
// Employee.h // Employee.cpp
class Employee { Employee::Employee(string name, int years)
{
public: myName = name;
Employee(string name, int years); virtual int hours() myYears = years;
const; }
virtual string name() const; int Employee::hours() const {
virtual double salary() const; return 40;
virtual int vacationDays() const; virtual string }
vacationForm() const; virtual int years() const; string Employee::name() const {
return myName;
private: }
string myName; double Employee::salary() const {
int myYears; return 40000.0 + (500 * myYears);
}; }
int Employee::vacationDays() const {
return 10;
}
string Employee::vacationForm() const { return "yellow";
}
int Employee::years() const {
return myYears;
}

10
Polymorphisme et Pointeurs
 Un pointeur de type T peut pointer sur n'importe quelle sous-classe
de T.
Employee* edna = new Lawyer("Edna", "Harvard", 5);
Secretary* steve = new LegalSecretary("Steve", 2);

 Lorsqu'une fonction membre est appelée sur edna, edna se


comporte comme un Lawyer.
 C'est parce que les fonctions d’ employee sont déclarées virtuelles.
 Vous ne pouvez appeler aucun membre unique de Lawyer sur edna
(e.g sue). Pourquoi?

 Vous ne pouvez appeler aucun membre unique de LegalSecretary


sur steve. Pourquoi?

11
Polymorphisme : Exemple
 En castant vous pouvez utiliser l’extra fonctionnalités de l’objet
Employee* edna = new Lawyer("Edna", "Harvard", 5); // ok
edna->vacationDays();
edna->sue("Stuart"); // compiler error
((Lawyer*) edna)->sue("Stuart"); // ok
 Vous ne devriez pas casté un pointeur vers quelque chose qu'il ne
l'est pas. Il compilera, mais le code plantera (ou se comportera de
manière imprévisible) lorsque vous essayerez de l'exécuter
Employee* paul = new Programmer("Paul", 3);
paul->code(); // compiler error
((Programmer*) paul)->code(); // ok
((Lawyer*) paul)->sue("Marty"); // crash!

12
Mystère du polymorphisme
class Snow { class Sleet : public Snow {
public: public:
virtual void method2() { virtual void method2() {
cout << "Snow 2" << endl; cout << "Sleet 2" << endl;
} Snow::method2();
virtual void method3() { }
cout << "Snow 3" << endl; virtual void method3() {
} cout << "Sleet 3" << endl;
}; }
class Rain : public Snow { };
public: virtual void method1() { class Fog : public Sleet {
cout << "Rain 1" << endl; public: virtual void method1() {
} cout << "Fog 1" << endl;
virtual void method2() { }
cout << "Rain 2" << endl; virtual void method3() {
} cout << "Fog 3" << endl;
}; }
};

13
Diagramme de Classes
Diagramme de
classes du haut Snow
(superclasse) en method2()
Snow 2

bas. method3() Snow 3

Rain Sleet
method1() Rain 1 method2() Sleet 2 \ Snow 2
method2() Rain 2 method3() Sleet 3
(method3()) Snow 3

Fog
method1() Fog 1
method2() Sleet 2 \ Snow
method3() Fog 3

14
Problème Mystère
Snow* var1 = new Sleet();
var1->method2(); // What's the output?

 Pour trouver le comportement/output d'appels comme celui ci-


dessus :
 Regardez le type de la variable. Si ce type n'a pas ce membre :
COMPILER ERROR.
 Exécutez le membre. Puisque le membre est virtuel : se comporte
comme le type de l'objet, pas comme le type de la variable.

15
Diagramme de Classes
Snow* var1 = new Sleet();
Snow
var1->method2();
Snow 2
method2()
method3() Snow 3

Rain objet Sleet


method1() Rain 1 method2() Sleet 2 / Snow 2
method2() Rain 2 method3() Sleet 3
(method3()) Snow 3

Output? :
A. Snow 2
B. Rain 2 Fog
C. Sleet 2 / Snow 2 method1() Fog 1
D. COMPILER ERROR method2() Sleet 2 / Snow
method3() Fog 3

16
Diagramme de Classes
Snow* var2 = new Rain();
Snow
var2->method1();
Snow 2
method2()
method3() Snow 3

objet Rain Sleet


method1() Rain 1 method2() Sleet 2 / Snow 2
method2() Rain 2 method3() Sleet 3
(method3()) Snow 3

Output? :
A. Snow 1
B. Rain 1 Fog
C. Snow 1 /Rain 1 method1() Fog 1
D. COMPILER ERROR method2() Sleet 2 / Snow
method3() Fog 3

17
Diagramme de Classes
Snow* var3 = new Rain();
Snow
var3->method2();
Snow 2
method2()
method3() Snow 3

objet Rain Sleet


method1() Rain 1 method2() Sleet 2 / Snow 2
method2() Rain 2 method3() Sleet 3
(method3()) Snow 3

Output? :
A. Snow 2
B. Rain 2 Fog
C. Snow 2 /Rain 2 method1() Fog 1
D. COMPILER ERROR method2() Sleet 2 / Snow
method3() Fog 3

18
Conversion de type (cast)

Snow* var4 = new Rain();


((Rain *) var4)->method1(); // What's the output?

Si le problème mystère a une conversion de type, alors :

 Regardez le type de conversion. Si ce type n'a pas la méthode :


COMPILER ERROR.
 Remarque : si le type de l'objet n'était pas égal à ou n'était pas une
sous-classe du type de conversion, le code s'écraserait / aurait un
comportement imprévisible.

 Exécutez le membre. Puisque le membre est virtuel : se comporte


comme le type de l'objet, pas comme le type de la variable.

19
Diagramme de Classes
Snow* var4 = new Rain();
((Rain *) var4)->method1(); Snow
Snow 2
method2()
method3() Snow 3

objet Rain Sleet


Sleet 2 / Snow 2
cast method1() Rain 1 method2()
method2() Rain 2 method3() Sleet 3
(method3()) Snow 3

Output? :
A. Snow 1
B. Rain 1 Fog
C. Sleet 1 method1() Fog 1
D. COMPILER ERROR method2() Sleet 2 / Snow
method3() Fog 3

20
Diagramme de Classes
Snow* var5 = new Fog();
((Sleet*) var5)->method1(); Snow
Snow 2
method2()
method3() Snow 3

Rain Sleet cast


method1() Rain 1 method2() Sleet 2/Snow 2
method2() Rain 2 method3() Sleet 3
(method3()) Snow 3

Output? :
A. Snow 1 objet
B. Sleet 1 Fog
C. Fog 1 method1() Fog 1
D. COMPILER ERROR method2() Sleet 2/Snow 2
method3() Fog 3

21
Diagramme de Classes
Supposons qu’on a ajouté la
Méthode suivante à la classe de base Snow
Snow : method2() Snow 2
virtual void method4() { method3() Snow 3
cout << "Snow 4" << endl; method2();
}
Rain Sleet objet
method1() Rain 1 method2() Sleet 2/Snow 2
method2() Rain 2 method3() Sleet 3
(method3()) Snow 3

Snow* var6 = new Sleet();


var6->method4(); Output? :
A. Snow 1 Fog
B. Sleet 1
method1() Fog 1
C. Snow 4 method2() Sleet 2/Snow 2
Sleet 2 method3() Fog 3
Snow 2
D. COMPILER ERROR 22
Diagramme de Classes
Snow* var7 = new Sleet();
((Rain*) var7)->method1(); Snow
Snow 2
method2()
method3() Snow 3

cast Rain Sleet objet


method1() Rain 1 method2() Sleet 2/Snow 2
method2() Rain 2 method3() Sleet 3
(method3()) Snow 3

Output? :
A. Snow 1
B. Sleet 1 Fog
C. Fog 1 method1() Fog 1
D. COMPILER ERROR method2() Sleet 2/Snow 2
method3()
E. CRASH / UNDEFINED Fog 3

23
Virtual : constructeurs et destructeurs
 Les constructeurs ne peuvent pas être virtuels : déclarer
un constructeur comme virtual est une erreur de
compilation.

 Par contre les destructeurs doivent être virtuels


 Question : donnez un exemple ou on voit la nécessité de ce
choix?

24
Fonction virtuelle pure

virtual returntype name(params) = 0;

 Fonction virtuelle pure : déclarée dans le fichier .h de la superclasse


et mise à 0 (null). Une fonction absente qui n'a pas été
implémentée.
 Doit être implémentée par n'importe quelle sous-classe, ou elle ne peut
pas être utilisée.
 Un moyen de forcer les sous-classes à ajouter certains comportements
importants.

class Employee { ...


virtual void work() = 0; // every employee does
// some kind of work
};

25
Templates

Patrons de fonctions et de classes


Patrons de Fonctions et de Classes
Patrons de fonctions:
#include <iostream>
template <class T> T maximum (T x , T y) { return x<y?y:x;}
main ()
{
int i = 1 ;
int j = 2 ;
float x = 3.;
float y = 4.;
cout << maximum(i,j) << endl;
cout << maximum(x,y) <<endl;
}

Forme générale

template <class T , class U , class V> T myFonction( T x, U y,V z) { …}


Patrons de Fonctions et de Classes
Patrons de classes :
#include "Point.h"
template <class T=float> class Point {
private:
Tx;
T y;
public:
Point ( T a = 0 , T b = 0 ):x(a),y(b){ }
void Affiche () { cout << " x = " << x << " y " << y << endl ;}
};
Patrons de Fonctions et de Classes
#include <iostream>

#include "Point.h"

using namespace std;

int main()

Point <int> P1(1,2);

Point <float> P2(3.f,4.f);

Point <> P3(5.f,6.f);

P1.Affiche();

P2.Affiche();

P3.Affiche();

return 0;

Vous aimerez peut-être aussi