Vous êtes sur la page 1sur 32

HERITAGE

A l'origine

Impératif de réutiliser les portions de code
déjà écrites dans les classes utilisées pour
des projets antérieurs (comme cela se fait
dans les modules en C).

Pourquoi?

recopier est peu valorisant

recopier est coûteux en développement (écriture,
mise au point, tests)

recopier est coûteux en maintenance
(multiplication des erreurs)
HERITAGE
L'idée

Organiser ses propres classes en réutilisant
les classes existantes (les siennes ou celles
des autres (bibliothèques,…))

Une classe dérivée hérite de (récupère)
toutes les données membre et toutes les
fonctions membre de la classe de base
HERITAGE

L'idée (suite)

Une classe dérivée peut

accéder aux données membre et fonctions
membre de sa classe de base selon certaines
règles
(accès si public ou protected)

ajouter des données membre et fonctions
membre à sa classe de base

redéfinir* certaines fonctions membre de sa
classe de base
(*) définir des fonctions aux en-têtes identiques, à savoir noms identiques et
paramètres identiques. Différent de la surdéfinition ou surcharge!
HERITAGE
Le Principe

L'héritage simple structure un ensemble de
classes en une hiérarchie.

Au sommet, on trouve la classe
correspondant au concept le plus général.

Du sommet vers la base, on spécialise en
cas de plus en plus particuliers.

La relation d'héritage:

est_un

possède les caractéristiques de
HERITAGE
Le principe (suite)

Véhicule

Véhicule

Avion Voiture Bateau


Avion Véhicule Voiture Bateau

Avion Voiture Jeep


Bateau Voilier
Jeep Voilier

Hydravion Jeep Voilier


HERITAGE
Résumé

Permet de définir une classe qui enrichit ou
spécialise une classe existante

On peut dériver de une ou plusieurs classes
(héritage multiple très dangereux)

La classe dérivée peut accéder aux membres
publics et protégés de la classe de base

La classe dérivée ne peut pas accéder aux
membres privés de la classe de base
HERITAGE
Syntaxe

Héritage simple :

class MaClass : public MaClass_de_base


{
...
};


Héritage multiple :

class MaClass : public MaClasse_de_Base , public AutreClasse_de_deBase


{
...
};
HERITAGE
Construction

Quand un objet est construit, tous les
constructeurs sont appelés

Les constructeurs des classes de base
sont appelés AVANT les constructeurs des
classes dérivées

Exemple : Si A dérive de B qui dérive de
C:

Le constructeur de C est appelé

Le constructeur de B est appelé

Le constructeur de A est appelé
HERITAGE

Exemple
class D
{public :
D(int,int) ;
};
class E : public D // E dérivée de D
{public :
E(int,int,int) ;
};
// initialisation de la classe de base
E::E(int x,int y,int z) : D(x,y)
{
}
HERITAGE
Constructeurs

CAS 1 : Cas où le constructeur de la classe dérivée est synthétisé par le
compilateur (sans paramètres) : ce constructeur appelle le constructeur par
défaut de la classe de base (appel sans argument du constructeur synthétisé
ou d'un constructeur défini sans paramètres ou dont tous les paramètres ont
une valeur par défaut)
class A class A
class A { {
{ private: private:
private: public: public:
public: A(); A(T1 a1, T2 a2);
}; }; };

class B : public A class B : public A class B : public A


{ { {
private: private: private:
public: public: ...}; public:
}; OK : le constructeur synthétisé de B };
appelle le constructeur par défaut A NON : le constructeur synthétisé de B ne trouve
OK : le constructeur synthétisé de B appelle défini sans paramètres pas de constructeur par défaut (défini sans
le constructeur par défaut A (ici synthétisé) paramètres ou avec paramètres par défaut)
dans A
HERITAGE
Constructeurs (suite)

CAS 2 : Un constructeur explicitement défini de la
classe dérivée appelle le constructeur de la classe de
base en accord avec la liste d'initialisation dans l'en-
tête de sa définition. Si la liste ne mentionne pas de
constructeur, c'est le constructeur par défaut qui est
appelé (sans paramètres).
HERITAGE
Appels Constructeurs
class A
{
private:
public:
A(T1 a1, T2 a2);
};

class B : public A // B dérivée de A


{
private:
public:
B(T1 b1, T2 b2, T3 b3);
};

// définition des méthodes


B::B(T1 b1, T2 b2, T3 b3) : A(b1,b2)
{
}
OK : le constructeur de B appelle le constructeur de A précisé dans la liste
d'initialisation.
HERITAGE
Appels Constructeurs
class A
{
private:
public:
A();
A(T1 a1, T2 a2);
};
class B : public A
{
private:
public:
B(T1 b1, T2 b2, T3 b3);
};
// définition des méthodes
B::B(T1 b1, T2 b2, T3 b3)
{
} OK : le constructeur de B appelle le constructeur par défaut (sans paramètres et non
synthétisé ici) de A
HERITAGE
Appels Constructeurs
class A
{
private:
public:
};

class B : public A
{
private:
public:
B(T1 b1, T2 b2, T3 b3);
};

// définition des méthodes


B::B(T1 b1, T2 b2, T3 b3)
{
}

OK : le constructeur de B appelle le constructeur par défaut


(synthétisé) de A .
HERITAGE
Appels Constructeurs
class A
{
private:
public:
A(T1 a1, T2 a2);
};
class B : public A
{
private:
public:
B(T1 b1, T2 b2, T3 b3);
};
// définition des méthodes
B::B(T1 b1, T2 b2, T3 b3)
{
}

NON : le constructeur de B ne trouve pas le constructeur par défaut de A . Pas de


constructeur synthétisé ici.
HERITAGE
Destruction

Quand un objet est détruit, son destructeur et
celui de toutes les classes de base sont appelés

Le destructeur de la classe dérivée est appelé
en premier

Exemple : Si A dérive de B qui dérive de C :

Le destructeur de A est appelé

Le destructeur de B est appelé

Le destructeur de C est appelé
HERITAGE
Accès aux membres
Statut des membres de la classe dérivée en fonction du
statut des membres de la classe de base et du mode de
dérivation.

- les attributs privés, mais accessibles par l’instance


- les attributs inaccessibles : ils occupent de la place mémoire, mais personne ne peut plus
y accéder, y compris l’instance elle-même.
HERITAGE
Héritage privé

But : masquage du legs de la classe de base reçu par la classe dérivée
= technique de fermeture des accès à la classe de base

Syntaxe :
class MaClass : private MaClasse_de_Base
{
...
};

1) Un utilisateur de la classe dérivée MaClass ne pourra pas accéder aux membres


publics de la classe de base

2) Les fonctions membres de la classe dérivée peuvent accéder aux fonctions et


membres publics et protégés de la classe de base
HERITAGE
Héritage privé (suite)

Quand l’utiliser ?
Toutes les fonctions utiles de la classe de
base sont redéfinies dans la classe
dérivée et on ne veut pas laisser
l’utilisateur du programme accéder aux
fonctions de la classe de base
(exemple : Pile dérive de Tableau)
HERITAGE
Héritage privé (suite)
Ce type d'héritage s'utilise lorsque la classe dérivée n'est pas un cas particulier
Exemple: de la classe de base (bien que sa définition s'appuie sur celle de la classe de
base).
class Table class Pile : private Table
{ {
int nb; double *sommet;
double *tab; public:
public: Pile();
Table(); Pile(int taille);
Table(int taille); void empiler(double x);
Table(const Table&); double depile();
~Table(); bool isEmpty();
Table& operator=(const Table&); };
double& operator[](int);
};
L'implémentation de Pile s'appuie sur celle de Table, mais il est nécessaire de cacher à l'utilisateur la
possibilité d'accéder à n'importe quel élément de la pile par l'opérateur []; pour cette raison on choisit
l’héritage privé.
HERITAGE
Rétablissement des droits d'accès
Il est possible de rétablir les droits d'accès modifiés par la dérivation pour
rétablir les droits d'origine de ces attributs ou méthodes.
Le rétablissement ne concerne que les membres déclarés public ou
protected dans la classe de base.
class A
class B : private A int main()
{
{ {
public: public: B b;
using A::a1; b.a1 = 1; // OK
int a1; }; b.a2 = 1; // Illégal
int a2; return 0;
};
};
HERITAGE

Constructeur par copie


Rappel : le compilateur génère un constructeur par copie trivial si le
concepteur n'en écrit pas.
Le constructeur par copie généré pour une classe dérivée appelle
implicitement les constructeurs par copie des classes de base. Ce n'est pas
le cas si on écrit soi-même le constructeur par copie.
HERITAGE
Constructeur par copie
HERITAGE
Constructeur par copie
La meilleure solution consiste là encore à utiliser les listes
d'initialisation des constructeurs :
HERITAGE
Opérateurs

Tous les opérateurs sont hérités

Attention à l’opérateur = (affectation) :
surcharge de l’opérateur = dans la classe dérivée
: il faut appeler celui de la classe de base (pas d’appel
implicite comme dans le cas du constructeur)
HERITAGE
Opérateurs (suite)

Si opérateur = non surchargé dans la classe
dérivée:

affectation de la partie héritée de la classe de base
selon la surcharge de l'opérateur = dans la classe de
base

affectation membre à membre (par défaut) pour les
membres propres à la classe dérivée
HERITAGE
Redéfinition des attributs
Il est possible de redéfinir un attribut dans la classe dérivée. L'attribut
redéfini masque celui issu de la classe de base. L'opérateur de
résolution de portée (::) permet cependant d'accéder à l'attribut
masqué :
class A int x;
{ void B::f() Trois variables de
public: { même nom !!!
int x; x++; // incrémente B::x
}; A::x++; // incrémente A::x
class B : public A ::x++; // incrémente x global
{ };
public:
int x;
void f();
};
HERITAGE
Redéfinition des méthodes
De la même façon, une méthode masque toutes les
méthodes de même nom définies dans la classe de base.
class A int main()
{ {
public: B b;
void f(int); b.f(1); // Erreur : 'f' fonction ne prend pas de paramètre
void f(); b.A::f(1); // OK
}; b.f(); // OK, appelle B::f()
class B : public A return 0;
{ };
public:
void f();
};
HERITAGE
Conversions d’objets
Par un héritage public, une classe B dérivée de A est considérée comme
une "sorte" de A. Quel que soit l'objet a de type A et l'objet b de type
B dérivé publiquement de A, tous les services offerts par a sont aussi
offerts par b
=> donc b peut remplacer a
Pour des classes en relation de dérivation publique, le compilateur
effectue certaines conversions implicites :
• objet de la classe dérivée => objet de la classe de base ;
• référence sur un objet de la classe dérivée => référence sur objet
classe de base ;
• pointeur sur un objet de la classe dérivée => pointeur sur objet classe
de base.
Cette conversion implicite n'existe pas si l'héritage est privé ou protégé.
HERITAGE
Conversions d’objets (suite)
On réalise les appels :
X xob;
Soient les classes: Y yob;
class X {}; P pob;
class Y : public X {};
fx1(yob); // Classe dérivée => base
class P : private X {};
fx2(&yob); // Idem

fy1(xob); // Erreur, pas dans ce sens


fy2(&xob); // Erreur, pas dans ce sens

fx1(pob); // Erreur, héritage privé


fx2(&pob); // Erreur, héritage privé

xob = yob; // Classe dérivée => base


Et soient les fonctions yob = xob; // Erreur, pas dans ce sens
suivantes: xob = pob; // Erreur, héritage privé
void fx1(X x);
void fx2(X* x); D’après les héritages choisis :
void fy1(Y y); • « Tous les Y sont des X », mais « Tous les X ne sont pas des Y »
void fy2(Y* y); • « Les P ne sont pas des X »
Le compilateur se charge de réaliser automatiquement les
conversions autorisées (ici seulement de la classe Y vers la classe X).
HERITAGE
Conversions d’objets (suite)

Pour un pointeur ou une référence, il est possible de distinguer :


• le type statique du pointeur/référence : il peut être déterminé à la compilation,
par une analyse statique du code source ;
• le type dynamique : il est déterminé par la valeur courante, et peut changer en
cours d'exécution.
HERITAGE
Conversions d’objets (suite)
Grâce à ces conversions implicites de pointeurs et de références, tout
objet peut être traité comme un objet plus général. Cela permet de
traiter collectivement un ensemble d'objets de types différents :

Vous aimerez peut-être aussi