L’HÉRITAGE EN C++
Olfa FAKHFAKH POO – ENSI – 2011/2012
Plan
2
1. Concept & Principe
2. Visibilité entre héritier
3. Redéfinition des méthodes
4. Constructeurs & destructeurs
5. Type d’héritage
6. Compatibilité entre classe de base et classe dérivée
7. Méthodes non héritées
8. Héritage multiple
1. Concept & Principe
3
Étendre un type abstrait :
maj. des services et/ou des comportements
Mécanisme permettant de :
ajouter de nouvelles fonctionnalités à une classe existante
changer un peu le comportement de certaines méthodes d’une classe déjà
existante
On veut faire cela sans rien changer à la classe déjà existante
On définira donc une nouvelle classe qui héritera de la classe existante
En C++, on parlera de classe de base et de classe dérivée
(suite)
4
Une classe dérivée hérite les attributs et les méthodes
de la classe de base (à l’exception de quelques unes)
Mais, une classe dérivée peut redéfinir une méthode.
Ainsi, c’est cette méthode redéfinie qui sera appelée
pour un objet de cette classe, et non pas la méthode
originale de la classe supérieure
Exemple :
5
Voyons d’abord la classe Employé:
class Employe {
public:
Employe(string nom_employe, double salaire_initial);
void set_salaire(double nouveau_salaire);
double get_salaire() const;
string get_nom() const;
private:
string nom;
double salaire;
};
Exemple : classe dérivée
6
héritage
class Directeur : public Employe {
public:
Directeur(string nom_employe, double salaire_initial);
void ajouter_employe(Employe* employe);
Employe get_employe(string nom) const;
private:
/* Collection d’employés */ employes_supervises;
};
Attribut spécifique à
Nouvelles méthodes
l’héritier
Instanciation d’une classe dérivée
7
Employe P1("Ali",100);
P1
Employe P2("Hédi",120);
Ali
Employe P3("Amira",110);
100
Directeur D("Salah",130);
D.ajouter_employe( &P1);
D.ajouter_employe( &P2);
D P2 P3
Salah Hédi
120 Amira
130
110
…
Représentation de l’héritage (UML)
8
Employe
0..* - nom
- salaire
Directeur
supervise
Remarques
9
L’héritage est une relation de type « est un »
Pour la classe Directeur qui est une classe dérivée de la classe Employe.
Tout directeur est aussi un employé, ce qui est tout à fait conforme à
l’intuition
Il ne faut pas confondre l’héritage avec l’agrégation (ou la
composition)
Il pourrait être attirant d’utiliser l’agrégation(ou la composition) au
lieu de l’héritage
Du point de vue technique, le résultat peut paraître équivalent. Mais
il s’agit de deux concepts tout à fait différents
Exemple 2: un point coloré est un point
10
class point
{ /* déclaration des membres
privés */ class pointcol : public
int x ; point
int y ; {
/* déclaration des membres short couleur ;
publics */
public :
public :
void colore (short cl)
void initialise (int, int) ;
{ couleur = cl ; }
void deplace (int, int) ;
} ;
void affiche () ;
} ;
Exemple 2: un point coloré est un point
11
Chaque objet de type pointcol peut
main()
alors faire appel :
{ pointcol p ; • aux méthodes publiques de pointcol
p.initialise (10,20) ; (ici colore),
p.colore (5) ; • aux méthodes publiques de la classe
p.affiche () ; de base point (ici initialise, deplace et
affiche).
p.deplace (2,4) ;
p.affiche () ;
}
Je suis en 10 20
Je suis en 12 24
2. Visibilité entre héritier
12
• Lors de l’héritage public, les attributs privés de la classe de
base deviennent inaccessibles pour les classes dérivées.
Il faut donc utiliser les méthodes publiques de la classe de
base afin d’accéder à ces attributs
• Les attributs protégés de la classe de base seront accessibles
par les classes dérivées, mais ne seront pas accessibles par les
utilisateurs de la classe dérivée.
Utilisation des membres de la classe de base
dans une classe dérivée
13
class pointcol : public point
void pointcol::affichec ()
{…
{affiche () ;
void affichec ()
cout << " et ma couleur est : " <<
{ cout << "Je suis en " << x << " "
couleur << "\n" ;
<< y << "\n" ;
}
cout << " et ma couleur est : " <<
couleur << "\n" ;}
}; void pointcol::initialisec (int abs, int
ord, short cl)
{ initialise (abs, ord) ;
Une méthode d’une classe dérivée
n’a pas accès aux membres privés couleur = cl ;
de sa classe de base. }
Les membres protégés
14
Les membres protégés sont :
inaccessibles à l'utilisateur de la classe (comme des membres
privés).
accessibles aux membres d'une éventuelle classe dérivée,
inaccessibles aux utilisateurs de cette classe dérivée
Les membres protégés
15 class pointcol : public point
{
class point
short couleur ;
{ protected :
public :
int x, y ;
void affiche ()
public : { cout << "Je suis en " << x << " " <<
point ( ... ) ; y << "\n" ;
affiche () ; cout << " et ma couleur est " << couleur
<< "\n" ;
.....
}
}; Main(){ ….
point p; };
p.x=0;
p.y=0;
}
Exemple : visibilité
16
class A {
private : class B : public A {
int i; public :
public : void f()
int j; {
protected : void main() i = 0;
int k; { j = 0;
}; A a; k = 0;
a.i = 0; }
a.j = 0; };
a.k = 0;
}
Exemple : Horloge
17
Soit la classe Horloge, qui permet d’obtenir
l’heure locale, de deux façons:
normale : (23:45)
à l’américaine [am/pm] (pm 11:45)
Cas : Horloge sans héritage
18
void Horloge::afficher()
class Horloge {
{
CC hh,mm;
if ( a_americaine)
bool a_americaine;
// am hh:mm ou pm hh:mm
public :
else
Horloge(int h, int m
// hh:mm
bool americaine = false)
}
:hh(h,24),mm(m,60)
{a_americaine = amercicaine;}
void operator ++(); void main()
int valh(); {
int valm(); Horloge H1(22,50,true);
void afficher(); Horloge H2(23,55,false);
}; }
Cas : Horloge avec héritage
19
class Horloge {
Horloge CC hh,mm;
public :
Horloge(int h, int m);
int valh();
Clock
int valm();
void afficher();
class Clock : public Horloge };
{
void main()
public :
{
Clock(int h, int m);
Horloge H1(22,50);
...
Clock H2(23,55);
};
}
3. Redéfinition des méthodes de base
20
• Les méthodes de la classe de base peuvent être
redéfinies dans la classe dérivée.
• Les méthodes redéfinies de la classe de base
demeurent accessibles via l'opérateur de résolution
de portée ("::").
Cas : Clock – classe dérivée
21
void Clock::afficher()
class Clock : public Horloge
{
{
if ( valh() <= 12 )
public :
// am h:m
Clock(int h, int m);
else
void afficher ();
// pm h-12:m
};
}
void main()
{
Clock H1(22,50);
H1.afficher();
H1.Horloge::afficher();
}
Exemple :
Redéfinition de méthodes
22
void Horloge::afficher()
{ cout<< valh() << ":" << valm() << endl; }
void Clock::afficher()
{
if ( valh() < 12 ) {
cout << "am ";
Appel de la fonction
Horloge::afficher();
afficher() du parent
} else {
cout << "pm " << valh() -12 << ":" << valm() << endl;
}
Redéfinition des fonctions membres d’une
classe dérivée void pointcol::affiche ()
{
23
point::affiche () ; // appel de affiche de la classe point
cout << couleur << "\n" ;
class pointcol : public point{
}
public :
void colore (short cl) void pointcol::initialise (int abs, int ord, short cl)
{ couleur = cl ; } {
void affiche () ; point::initialise (abs, ord) ;
void initialise (int, int, short); couleur = cl ;
}; }
Il faut alors faire appel à l'opérateur
de résolution de portée (::) pour
localiser convenablement la méthode
void main(){
pointcol p ; voulue
p.initialise (10,20, 5) ; p.affiche () ;
p.point::affiche () ; // pour forcer l'appel de affiche de point
p.deplace (2,4) ; p.affiche () ;
p.colore (2) ; p.affiche () ;
}
Redéfinition des membres données d’une
classe dérivée
24
class A
{ .....
class B : public A NB. le membre a défini dans B
int a ; s'ajoute au membre a hérité de A ;
{ float a ;
char c ; il ne le remplace pas.
.....
.....
};
};
main()
{ B b;
b.a; // fait référence au membre a de type float de b.
b.A::a; // fait référence au membre a de type int de a.
};
Redéfinition et surdéfinition
25
class A lorsqu’une fonction est redéfinie
{ public : dans une classe dérivée, elle
class B : public A masque une fonction de même
void f(int n) { ..... }
signature de la classe de base.
void f(char c) { ..... } { public :
// f est surdéfinie dans A void f(float x) { ..... }
// on ajoute une troisème
}; définition dans B
};
main()
{ int n ; char c ; A a ; B b ;
a.f(n) ; // appelle A:f(int) (règles habituelles)
a.f(c) ; // appelle A:f(char) (règles habituelles)
b.f(n) ; // appelle B:f(float) (alors que peut-être A:f(int) conviendrait)
b.f(c) ; // appelle B:f(float) (alors que peut-être A:f(char) conviendrait)
}
Redéfinition et surdéfinition class B : public A
{ public :
class A
26
void f(float x) { ..... }
{ public :
void f(int x) { ..... }
void f(int n) { ....}
void f(char c) { ...} } ;
void g(int n) { ....}
main()
void g(char c) { ....} { int n ; char c float x; A a ; B b ;
} ; a.f(n) ; // appelle A:f(int) (règles habituelles)
a.f(c) ; // appelle A:f(char) (règles habituelles)
b.f(n) ; // appelle B:f(int)
b.f(x) ; // appelle B:f(float)
b.f(c) ; // appelle B:f(int)après conversion de c en int et no
pas A:f(char) (alors que peut-être A:f(char) conviendrait)
b.g(n) ; // appelle A:g(int)
b.g(x) ; // appelle A:g(int) ou A:g(char)
b.g(c) ; // appelle A:g(char)
Redéfinition et surdéfinition
27
class A
{ public :
void f(int n) { ..... } class B : public A Une redéfinition d’une méthode
void f(char c) { ..... } { public : dans une classe dérivée cache
en quelque sorte les autres.
}; void f(int, int) { ..... }
};
main()
{ int n ; char c ; B b ;
b.f(n) ; // erreur de compilation
b.f(c) ; // erreur de compilation
}
4. Constructeur et destructeur
28
Lors de la création d’un objet d’une classe dérivée, les
constructeurs sont appelés dans l’ordre suivant:
1) Les constructeurs des objets attributs de la classe de
base,
2) Le constructeur de la classe de base,
3) Les constructeurs des objets attributs de la classe
dérivée;
4) Le constructeur de la classe dérivée.
Les destructeurs sont appelés en ordre inverse des
constructeurs.
Exemple : constructeur
29
void main()
class A {
{
public :
B b;
A() { cout << "A::A()" << endl;}
cout << "**" << endl;
~A() { cout << "A::~A()" << endl;}
}
};
class B : public A { A::A()
public : B::B()
B() { cout << "B::B()" << endl;} **
~B() { cout << "B::~B()" << endl;} B::~B()
}; A::~A()
Transmission d'informations entre
30
constructeurs
class point
{… class pointcol : public point
public : {…
point (int , int ); public :
… pointcol (int, int, short) ;
~pointcol ();
~point ();
…
… };
}; …
pointcol::pointcol (int abs, int ord, short cl) :
point (abs, ord)
{
Couleur = cl;
}
Constructeur de Horloge & Clock
31
Horloge::Horloge(int h, int m)
: hh(h), mm(m)
{
Clock::Clock(int h, int s) : Horloge(h,s)
{
}
5. Type d’héritage
32
Il existe trois différents types d’héritage, soient :
public, private et protected.
Le type d’héritage est spécifié après le symbole :
class point: public pointcol
Par défaut, le type d’héritage est privé.
class point : pointcol class point: private pointcol
Le Type d’héritage conditionne
la visibilité des héritiers
Héritage public
class ClasseDerivee : public ClasseBase
Classe de Classe
base dérivée
private Inaccessible
protected protected
public public
Les attributs private, protected et public de la classe de base
restent les mêmes pour la classe dérivée.
33
Héritage protected
class ClasseDerivee : protected ClasseBase
Classe de Classe
base dérivée
private Inaccessible
protected protected
public protected
Les attributs public de la classe de base deviennent protégés pour
la classe dérivée.
34
Héritage privé
class ClasseDerivee : private ClasseBase
Classe de Classe
base dérivée
private Inaccessible
protected private
public private
Tous les attributs de la classe de base deviennent private pour la
classe dérivée.
35
Héritage privé: exemple
36
class pointcol : private point{
class point { public :
public : pointcol (...) ;
Void point(); void colore (...) ;
};
void affiche () ;
void deplace (...) ;
};
void main()
{
pointcol p ;
p.affiche ();
p.deplace (...);
p.colore (...);
}
Droits d'accès sur les membres hérités
mot clé utilisé pour l'héritage
Accès aux données public protected private
public public protected private
mot clé utilisé
pour les protected protected protected private
champs et les
méthodes
private private private private
37
6. Compatibilité entre classe de base
et classe dérivée
38
Cette compatibilité entre une classe dérivée et sa classe de
base ne s'applique que dans le cas de dérivation publique.
Elle se résume à l'existence de conversions implicites :
d'un objet d'un type dérivé dans un objet d'un type de base,
d'un pointeur (ou d'une référence) sur une classe dérivée en un
pointeur (ou une référence) sur une classe de base.
On traduit souvent ces propriétés en disant que l’héritage
réalise une relation « est un» entre la classe dérivée et la
classe de base :
tout objet de type pointcol est un point, mais tout objet de
type point n’est pas un pointcol.
Conversion d'un type dérivé en un type de base
39
Entraîne une conversion de b dans le type
point a ; point et l'affectation du résultat à a.
pointcol b ; Cette affectation se fait, suivant les cas :
a=b; • par appel de l'opérateur d'affectation
(de la classe point) si celui-ci a été
surdéfini,
• par emploi de l'affectation par défaut
dans le cas contraire
point a ;
pointcol b ;
l'affectation suivante serait rejetée
b=a ;
Conversion de pointeurs
40
class point
{ int x, y;
class pointcol : public point
public :
{ short couleur ;
.....
public :
void affiche () ;
..... point * adp ;
};
void affiche () ; pointcol * adpc ;
};
adp = adpc ;
une conversion du type pointcol *
dans le type point *. adpc = adp ;
adpc = (pointcol *) adp ;
7. Méthodes non héritées
41
Les classes dérivées n’héritent pas :
• Des constructeurs
(défaut, paramètres, copie);
• Du destructeur;
• De l’opérateur d’affectation;
• Des relations d’amitié.
Le constructeur de recopie et l’héritage
42
1. Si la classe dérivée ne définit pas de constructeur de recopie,
il y aura appel du constructeur par recopie par défaut de la
classe dérivée, lequel procédera ainsi :
appel du constructeur par recopie de la classe de base (soit
celui qui y a été défini, soit le constructeur par recopie par
défaut) ;
initialisation des membres donnée de la classe dérivée qui ne
sont pas hérités de la classe de base.
Le constructeur de recopie et l’héritage
43
2. Si la classe dérivée définit explicitement un constructeur par
recopie :
Le constructeur de recopie de la classe dérivée doit prendre en
charge l’intégralité de la recopie de l’objet, et non seulement de
sa partie héritée.
Il faut tenir compte de ce que l’appel de ce constructeur par
recopie entraînera l’appel du constructeur de la classe de base
mentionné dans son en-tête
class A { ... } ;
class B : public A { ... } ;
Appel du constructeur par recopie de A
auquel sera transmise la partie de B héritée de A
B (B & b) : A(b) ; (grâce aux règles de compatibilité entre
classe dérivée et classe de base)
Exemple de constructeur de recopie:
44
class point
class pointcol : public point
{ int x, y ;
{ char coul ;
public :
public :
point (int abs=0, int ord=0)
pointcol (int abs=0, int ord=0, int cl=1)
{ x = abs ; y = ord ;} : point (abs, ord)
point (point & p) { coul = cl ; }
{ x = p.x ; y = p.y ;} pointcol (pointcol & p) : point (p)
}; { coul = p.coul ;}
};
il y aura conversion implicite
de p dans le type point
L’opérateur d’affectation et l’héritage
45
1.
• Si on a un opérateur= dans la classe de base;
• Si aucun opérateur= dans la classe dérivée;
Alors, le compilateur va appeler l’opérateur= de la classe de
base et recopie attribut par attribut les attributs de la classe
dérivée.
L’opérateur d’affectation et l’héritage
46
2. Si la classe dérivée surdéfinit l'opérateur =
L'affectation de deux objets de type B fera alors
nécessairement appel à l'opérateur = défini dans B.
Celui de A ne sera pas appelé, même s'il a été surdéfini.
Il faudra donc que l'opérateur = de B prenne en charge tout
ce qui concerne l'affectation d'objets de type B, y compris pour
ce qui est des membres hérités de A
Exemple de l’opérateur d’affectation:
class point
47 class pointcol : public point
{ int x, y ;
{ int coul ;
public :
public :
point (int abs=0, int ord=0)
pointcol (int abs=0, int ord=0, int cl=1) :
{ x = abs ; y = ord ;} point (abs, ord)
point & operator = (point & a) { coul = cl ; }
{ x = a.x ; y = a.y ; pointcol & operator = (pointcol & b)
return * this ;} { coul = b.coul ;
}; return * this ;}
main()
};
{ pointcol p(1, 3, 10) , q(4, 9, 20) ;
q=p;
l'opérateur = défini dans la classe point
q.affiche () ;
n'a pas été appelé lors d'une affectation
} q avant = pointcol : 4 9 20
entre objets de type pointcol.
operateur = de pointcol
q apres = pointcol : 4 9 10
L’opérateur d’affectation et l’héritage
48
Comment forcer, dans une classe dérivée, l'utilisation de
l'opérateur = surdéfini dans la classe de base :
class pointcol : public point
{….
pointcol & operator = (pointcol & b)
{point * ad1, * ad2 ;
ad1 = this ; // conversion pointeur sur pointcol en pointeur sur point
ad2 = & b ; // idem
* ad1 = * ad2 ; // affectation de la "partie point" de b
coul = b.coul ; // affectation de la partie propre à pointcol
return * this ;
};
8. Héritage multiple
49
Une classe héritière possède plusieurs classes parentes
class B :
class A :
{public :
{public :
int g() {return 2*i;}
int f() {return i;}
private :
private :
int j;
int i;
};
};
class C : public A, public B
{
public :
int h();
}
Pb1 :
Que se passe-t-il si un même identifiant est utilisé dans deux classes parentes ?
50
class B :
class A :
{public :
{public :
int f() {return 2*i;}
int f() {return i;}
private :
private :
int i;
int i;
};
};
class C : public A, public B
{
public :
int g();
}; Redéfinir f() dans C
Exemple : pointcoul hérite de point et de coul
51 class point Class coul
{int x, int y; { short couleur;
public :
public :
coul (int );
point (int , int );
~coul();
~point ();
Affiche();
Affiche(): };
}; Class pointcoul : public point, public coul
{…
public :
public :
pointcoul::pointcoul (int abs, int ord, int cl) : point (abs,
ord), coul (cl) { … }
~pointcoul ();
void affiche ()
{ point::affiche () ; coul::affiche () ;}
};
Pb2 :
Héritage répété : un hydravion hérite des classes d’avion et bateau qui toute deux héritent de véhicule.
Que deviennent les caractéristiques de la classe répétée ? Sont-elles dupliquées ou fusionnées ?
52
class A {
public :
int f() {return i;}
};
class B : public A { class C : public A {
}; };
class D : public B, public C {
public :
int g(); Héritage virtuel :
}; class B : virtual public A
Conclusion
53
• Les classes dérivées sont un mécanisme simple pour définir une
nouvelle classe en ajoutant des facilités à une classe existante
sans reprogrammer ou recompiler la classe de base.
• En utilisant les classes dérivées d’une classe existante, on définit
une interface commune aux classes dérivées de telle manière
que les objets de ces classes dérivées sont manipulés de façon
identique par certaines parties du programme.
Réutilisation
• On peut ainsi utiliser l’héritage pour les besoins de
généralisation, de réutilisation.