L'héritage
Sommaire du chapitre :
2. Héritage public/privé de classes
3. Propriété private utilisée sélectivement
4. Paramètres d'initialisation
class A { public : int Var_A };
class B: public A
{
public :
A Var_B1 , Var_B2;
int Var_B3;
};
Dans cet exemple la classe B contient trois fois la classe A :
- une fois par héritage
- deux fois par emboîtement selon la technique que nous avons vue précédemment.
Pour hériter publiquement une classe A dans une classe C on ajoute : public A après le nom B.
Le membre hérité n'a pas de nom de champ.
Tous les membres d'une classe héritée font partie de la nouvelle classe.
On peut aussi concevoir que la classe B consiste en une classe A à laquelle
on a ajouté le corps de la classe B en appendice.
On verra par la suite que les objets de type B peuvent être manipulés par les
objets et les procédures qui ne connaissent que la classe A.
Bien entendu ceux-ci ne peuvent pas accéder au membre Objet.Var_B3 , ni aux
membres Objet.Var_B1 et Objet.Var_B2 , ni aux autres membres emboîtés dans B.
Accès aux composantes
B Objet;
Objet.Var_A = 1;
Objet.Var_B1.Var_A = 1;
Objet.Var_B2.Var_A = 1;
Différences entre l'héritage et l'emboitement
- L'héritage n'ajoute pas de nouveau nom de membre local à B.
Si l'on instancie un objet Objet de classe B, on peut référencer le membre Var_A
hérité par Objet.Var_A), alors que les deux membres obtenus par emboîtage sont
référencés par Objet.Var_B1.Var_A et Objet.VarB2.Var_A.
Contrairement à ce qui se passe avec l'emboîtement, il est impossible d'hériter
directement plus d'une fois la même classe dans une autre classe.
On ne pourrait en effet pas identifier les différents héritages.
- La deuxième différence réside dans les possibilité de sous-typage :
l'objet peut être vu comme appartenant à la classe héritière ou à la classe
héritée (cf le chapitre sur le sous-typage).
2. Héritage public/privé de classes
class MaClasse :
private Classe1,
public Classe2, ...
{
<Déclaration des membres>
};
De façon plus générale, il est possible d'hériter les classes de façon privée
ou publique.
Pour hériter plusieurs classes, on place deux points après le nom de la classe
qui hérite, puis les noms des classes héritées, séparées par des virgules.
Devant chaque nom il est possible de mettre le mot-clé private ou public.
Si l'on ne met rien, le compilateur considère que la classe est héritée en mode
private.
Les membres publics des classes héritées de façon publique sont accessibles
normalement depuis l'extérieur, comme les membres publics emboîtés localement.
Pour qu'on puisse atteindre un membre d'une classe héritée, depuis l'extérieur
de l'objet, il faut donc que public apparaisse deux fois, une fois devant l'héritage
et une fois devant le membre.
Aucun des membres d'une classe héritée de façon privée, n'est accessible de
l'extérieur.
3. Propriété private utilisée sélectivement
class A
{
public : int x,y ;
protected : int z ;
};
class B :
private A
{
public : A::x ;
protected : A::y ;
protected : A::z ;
};
Considérons une classe A qui contient des éléments publics et d'autres privés.
Cette classe est héritée de façon privée dans une autre classe, B.
Si l'on ne veut faire apparaître aux objets extérieurs qu'un nombre limité des
membres de la classe A , on peut les requalifier indépendamment les uns des
autres, sans toutefois mettre un accès plus étendu que celui qui était défini
à l'origine.
Private ne peut donc être changé dans aucun cas.
Ici, le membre x est remis au mode d'accès public , le membre y est remis
à mi-chemin, au mode protected , et le membre z est remis à son mode
d'origine, protected.
4. Paramètres d'initialisation
Si l'on déclare un objet d'une classe donnée entre les accolades d'une fonction
ou même dans une paire d'accolades à l'intérieur d'une fonction, cet objet est
créé et son constructeur appelé au moment de la déclaration.
L'objet est détruit et son destructeur appelé lorsque l'on rencontre l'accolade
droite.
Ci-dessous, la ligne /* REPERE */ étant définie à l'intérieur d'une fonction,
on peut y placer un objet A avec ses paramètres d'initialisation :
class A { A(int); } ;
class B { B(char); } ;
class XXX : private A, public B
{
A x;
int k;
XXX(int i) : B('z') , x(12) , A(i) , k(o);
f() { A x(15); } /* REPERE */
};
Cette liste contient les paramètres qui initialisent les classes héritées et
ceux qui initialisent les membres.
L'ordre d'initialisation est donné par cette liste.
Elle a force de précédence sur l'ordre d'héritage.
Toutefois ce sont les classes héritées qui sont initialisées d'abord, puis les
membres.
Dans le cas présenté ici, on initialise la classe B puis la classe A , alors
qu'elles sont héritées dans l'autre ordre
L'objet x est initialisé en dernier.
Le compilateur annonce une erreur s'il n'y a pas de constructeur sans paramètre pour une variable d'une classe héritée et qu'il n'y a pas d'initialisation sur la ligne du constructeur.
5. Héritage multiple indirect de la même classe
Ci-dessous nous supposons avoir deux classes A et B qui héritent toutes les
deux de la classe X.
La classe C peut hériter de A et de B. L'objet C contient donc deux fois les
objets de classe X.
Pour y accéder, il faut les identifier en faisant précéder le champ par le nom
de la classe intermédiaire.
class X { public : int x ; } ;
class A : public X { } ;
class B : public X { } ;
class C : public A, public B
{
A::x = 0 ;
} ;
void main()
{
C Objet;
Objet.A::x = 1 ;
Objet.B::x = 2 ;
}
6. Superposition d'héritages multiples
Dans ce paragraphe une première utilisation du mot-clé virtual est expliquée.
class X { public : int x } ;
class A : virtual public X { } ;
class B : virtual public X { } ;
class D : public A, public B
{
x = 0;
} ;
void main()
D Objet ;
Objet.x = 1 ;
} ;
Si l'on ajoute le mot-clé virtual devant l'héritage de X, l'objet X n'apparaît
qu'une fois.
Pour accéder à x , il n'y plus besoin de spécifier de quel x il s'agit.
class X { public : int x } ;
class A : virtual public X { } ;
class B : virtual public X { } ;
class C : public X { } ;
class D : public A, public B, public C
{
C::x = 0;
} ;
void main()
D Objet ;
Objet.B::x = 1 ;
Objet.C::x = 1 ;
} ;
Il est possible de faire simultanément des héritages virtuels et d'autres non
virtuels, ainsi que le montre l'exemple ci-dessus.
La désignation des deux X différents est faite de façon naturelle.