Vous êtes sur la page 1sur 39

A.U.

2018/2019

Chapitre 4

1 ATEL
 L’héritage appelé aussi dérivation est un principe de la POO
permettant de créer une nouvelle classe à partir d’une classe
existante tout en profitant des attributs et des méthodes de cette
dernière et en définissant de nouveaux attributs et de nouvelles
méthodes propres à la classe dérivée. Par cette méthode on crée
une hiérarchie de classes de plus en plus spécialisées. Cela a un
avantage majeur de ne pas partir toujours de zéro dans la
conception d’applications.
 La hiérarchie des classes constituée est aussi appelée
arborescence des classes. Au niveau de chaque nœud de
l’arborescence existe une relation de parenté. Le nœud est appelé
classe de base, ou classe parent, ou classe ancêtre, ou classe père
ou classe mère… et les classes dérivées sont appelés classes filles
ou sous-classes.
2
Cette notion pose bien entendu un certain nombre de questions
auxquelles il va falloir répondre :

 Comment protéger les données ?


 Comment appeler les constructeurs et les destructeurs des
différentes classes ?
 Comment faire avec les classes amies ?
 Peut-on hériter de plusieurs classes mères ?
 Etc…

3
Mode de
dérivation
Classe
mère

Classe
dérivée La classe B hérite de façon publique de
la classe A. Tous les membres publics ou
protégés de la classe A font partie de
l’interface de la classe B. 4
Le mode de dérivation, spécifié par un des mots public,
protected ou bien private, permet d’indiquer la protection des
données de la classe de base dans la dérivation :

 La classe dérivée pourra-t-elle accéder à tous les membres


(private, protected et public) de la classe de base ?
 Comment devra-t-on considérer, dans la classe dérivée, les
membres de la classe de base ?

5
Le mode de dérivation constitue donc une sorte de filtre dans le processus de
dérivation :
 Les données seront ou non accessibles par la classe fille et les classes
petites filles.
 Les membres privés ne sont jamais accessibles par les membres de la
classe dérivée.
 Le mode de dérivation ne concerne donc que les membres protected ou
public.
 En mode de dérivation public, le statut d’un membre reste inchangé : un
membre public reste public et un membre protected reste protected.
 En mode de dérivation protected, le statut d’un membre devient protected.
 En mode de dérivation private, le statut d’un membre devient private. C’est
à dire qu’il sera considéré dans la classe dérivée comme un membre private
donc inaccessible par les classes petites filles.

6
 Sile mode de dérivation n’est pas spécifié, le compilateur prend par
défaut le mode private pour une classe et public pour une structure.
 Le tableau suivant résume le statut des membres selon le mode de
dérivation :

7
C’est le mode d’héritage le plus courant. Il donne aux membres public et
protected, le même statut dans la classe dérivée.
Exemple

8
Le mode protected donne aux membres public et protected, le statut protected
dans la classe dérivée.
Exemple

9
Le mode private donne aux membres public et protected, le statut private
dans la classe dérivée.
Exemple

10
On peut redéfinir une fonction dans une classe
dérivée si on lui donne le même nom que dans la
classe de base. Il y aura ainsi deux fonctions de
même nom dans un objet, mais il sera possible de
les différencier en utilisant l’opérateur de résolution
de portée ::
Si A est la classe de base, B la classe dérivée et
« fonc » le nom de la fonction redéfinie :

▪ fonc ( ) : fera appel à la fonction redéfinie alors que


▪ A ::fonc ( ) : fera appel à la fonction de la classe de base.
11
Les constructeurs, constructeur de copie,
destructeurs et opérateurs d’affectation ne sont
jamais hérités.
 Les constructeurs par défaut des classes de bases
sont automatiquement appelés avant le
constructeur de la classe dérivée.
 Pour ne pas appeler les constructeurs par défaut,
mais des constructeurs avec des paramètres, on
doit employer une liste d’initialisation. L’appel des
destructeurs se fera dans l’ordre inverse des
constructeurs.

12
Exemple 1

Ce programme affiche:

Const. de la classe A
Const. de la classe B
Dest. de la classe B
Dest. de la classe A

int

13
Exemple 2 : Appel du constructeur avec paramètres

int
14
L’amitié ne se propage pas aux membres de la
classe dérivée et ne s’étend pas aux générations
suivantes.

15
 Conversion de variable

Il est possible de convertir implicitement une


instance d’une classe dérivée en une instance de
la classe de base si l’héritage est public.
L’inverse est interdit car le compilateur ne
saurait pas comment initialiser les membres de
la classe dérivée.

16
Exemple void traitement1(Vehicule v)
{ // ...
class Vehicule v.f1(); // OK
{ public: // …
void f1(); }
// ... int main()
}; {
class Voiture : public Voiture R25;
Vehicule traitement1( R25 );
{ public: // R25 est automatiquement
int f1(); //converti en une instance
}; de la //classe Vehicule
}
17
 Conversion de pointeur

Un pointeur (ou une référence) sur un objet d’une


classe dérivée peut être implicitement converti en
un pointeur (ou une référence) sur un objet de la
classe de base. Cette conversion n’est possible que
si l’héritage est public, car la classe de base doit
posséder des membres public accessibles (ce n’est
pas le cas d’un héritage protected ou private).

18
Exemple
C’est le type du pointeur qui détermine laquelle des
méthodes f1( ) est appelée
void traitement1(Vehicule *v)
{
// ...
v->f1( ); // OK
// ...
}
int main()
{ Voiture R25;
traitement1( &R25 );
}
19
En langage C++, il est possible d’utiliser l’héritage multiple :
Une classe peut hériter de deux ou plusieurs classes. Pour
chaque classe de base, on peut définir le mode d’héritage.

20
Exemple
class C: public B, public A
class A { int c;
{ public: public:
void fa() { /* ... */ } void fc();
protected: };
int a;
};
class B void C::fc()
{ public: { int i;
void fb() { /* ... */ } fa( );
protected: i = a + b;
int b; }
};

21
Ordre d’appel des constructeurs

Dans l’héritage multiple, les constructeurs sont


appelés dans l’ordre de déclaration de l’héritage.
Dans l’exemple suivant, le constructeur de la
classe C appelle le constructeur de la classe B,
puis celui de la classe A et en dernier lieu le
constructeur de la classe dérivée, même si une
liste d’initialisation existe

22
Exemple class C: public B, public A
class A // ordre d’appel des constructeurs
{ public: { public:
A(int n=0) { /* ... */ } C(int i, int j) : A(i) , B(j)
// ... { /* ... */ }
}; // liste d’initialisation
class B // ...
{ public: };
B(int n=0) { /* ... */ } int main()
// ... { C objet_c;
}; // appel des constructeurs
//B(), A() et C()
// ...
}
Les destructeurs sont appelés dans l’ordre inverse de celui des constructeurs.
23
Un objet de la classe D contiendra deux fois les données héritées de la
classe de base A, une fois par héritage de la classe B et une autre fois
par C. Il y a donc deux fois le membre « base » dans la classe D. L’accès
au membre « base » de la classe A doit se faire en levant l’ambiguïté

24
int main()
{ D od;
od.base = 0; // ERREUR, ambiguïté
od.B::base = 1; // OK
od.C::base = 2; // OK
}

Il est possible de n’avoir qu’une occurrence des membres


de la classe de base, en utilisant l’héritage virtuel.

Pour que la classe D n’hérite qu’une seule fois de la classe


A, il faut que les classes B et C héritent virtuellement de A

25
Il ne faut pas confondre ce statut
Exemple "virtual" de déclaration
int main() d’héritage des classes avec celui
{ D od; des membres virtuels que nous
od.base = 0; // OK, pas d’ambiguïté allons étudier. Ici, le mot-clé
} virtual précise au compilateur les
classes à ne pas dupliquer
26
Le polymorphisme permet l’utilisation d’une
même instruction pour appeler dynamiquement
des méthodes différentes dans la hiérarchie des
classes.
 En C++, le polymorphisme est mis en œuvre
par l’utilisation des fonctions virtuelles.

27
 Fonctions virtuelles
Exemple void traitement (Graphe &og)
{ // ...
class Graphe og.print();
{ public: // ...}
void print()
{ cout <<"Graphe::print()"; }
}; int main()
class Bouton: public Graphe {
{ public: // Qu’affiche ce programme ???
void print() Graphe X;
{ cout << "Bouton::print()"; } Bouton OK;
}; Fenetre Windows97;
class Fenetre: public Graphe traitement (X);
{ public: traitement (OK);
void print() traitement(Windows97);
{ cout << "Fenetre::print()"; }
}; }
28
Comme nous l’avons déjà vu, l’instruction og.print() de
traitement() appellera la méthode print() de la classe
Graphe car il va y avoir une conversion automatique des
deux objets en objet de type Graphe. La réponse est donc :

traitement(OK); // affichage de Graphe::print()


traitement(Window97); // affichage de Graphe::print()

29
Si dans la fonction traitement() nous voulons appeler la méthode
print() selon la classe à laquelle appartient l’objet, nous devons
définir, dans la classe de base, la méthode print() comme étant
virtuelle (C’est ça le polymorphisme) :

class Graphe
{ public:
// ...
virtual void print() const
{ cout<< "ObjetGraphique::print()" << endl;}
};

30
Pour plus de clarté, le mot-clé virtual peut être répété devant les
méthodes print() des classes Bouton et Fenetre :
class Bouton: public Graphe
{
public:
virtual void print() const
{ cout << "Bouton::print()"; }
};
class Fenetre: public Graphe
{
public:
virtual void print() const
{ cout << "Fenetre::print()"; }
};
31
On appelle ce comportement, le polymorphisme. Lorsque le
compilateur rencontre une méthode virtuelle, il sait qu’il faut
attendre l’exécution pour déterminer la bonne méthode à
appeler.

Le programme affichera :

traitement(OK); //affichage de Bouton::print()


traitement(Window97); //affichage de Fenetre::print()

32
 Destructeur virtuel

Il ne faut pas oublier de définir le destructeur comme


"virtual" lorsqu’on utilise une méthode virtuelle :

class Graphe
{ public:
//...
virtual ~Graphe() { cout << "fin de Graphe\n"; }
};
class Fenetre : public Graphe
{ public:
// ...
~Fenetre( ) { cout << "fin de Fenêtre "; }
};
33
int main()
{ Fenetre *Windows97 = new Fenetre;
Graphe *og = Windows97;
// ...
delete og;
// affichage de : fin de Fenêtre fin de Graphe
// si le destructeur n’avait pas été virtuel,
// l’affichage aurait été : fin de Graphe
}
Un constructeur, par contre, ne peut pas être déclaré comme
virtuel. Une méthode statique ne peut, non plus, être déclaré
comme 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, même si elle est redéfinie
avec un statut différent. Le statut de la classe de base prime.
34
Une méthode virtuelle pure est une méthode qui est déclarée
mais non définie dans une classe. Elle est définie dans une des
classes dérivées de cette classe. Une telle méthode se déclare
en ajoutant un = 0 à la fin de sa déclaration.
Exemple
class Graphe
{
public:
virtual void print() =0; //pas d’instance
//de cette méthode
}; // dans cette classe.

35
int main()
{ Graphe og; // ERREUR
// ...
}
Contrairement à une méthode virtuelle "normale", une méthode
virtuelle pure n’est pas obligé de fournir une définition pour
Graphe::print().

Une classe dérivée qui ne redéfinit pas une méthode virtuelle


pure est elle aussi abstraite.

36
Il arrive souvent que la méthode virtuelle définie dans la
classe de base sert de cadre générique pour les méthodes
virtuelles des classes dérivées. Ceci permet de garantir
une bonne homogénéité de votre architecture de classes.
Une classe est dite abstraite si elle contient au moins une
méthode virtuelle pure.
Étant donné que les classes abstraites ont des méthodes
non définies, il est impossible d’instancier des objets pour
ces classes. En revanche, on pourra les référencer avec
des pointeurs.

37
Le mécanisme des méthodes virtuelles pures et des classes abstraites
permet de créer des classes de base contenant toutes les
caractéristiques d’un ensemble de classes dérivées, pour pouvoir les
manipuler avec un unique type de pointeur. En effet, les pointeurs des
classes dérivées sont compatibles avec les pointeurs des classes de
base, on pourra donc référencer les classes dérivées avec des
pointeurs sur les classes de base, donc avec un unique type sous-
jacent : celui de la classe de base.
Cependant, les méthodes des classes dérivées doivent exister dans la
classe de base pour pouvoir être accessibles à travers le pointeur sur la
classe de base. C’est ici que les méthodes virtuelles pures
apparaissent. Elles forment un moule pour les méthodes des classes
dérivées, qui les définissent. Bien entendu, il faut que ces méthodes
soient déclarées virtuelles, puisque l’accès se fait avec un pointeur de
la classe de base et qu’il faut que ce soit la méthode de la classe réelle
de l’objet (c’est à dire la classe dérivée) qui soit appelée.
38
Exemple

int main()
{
Graphe *og1, *og2, *TabMixte[100];
og1= new Bouton;
og2= new Fenetre;
og1->print();
og2->print ();
TabMixte[0] = new Bouton;
TabMixte[1] = new Fenetre;
//Etc…
}

39

Vous aimerez peut-être aussi