Héritage entre classe
La puissance du langage C++ réside dans le fait de pouvoir réutiliser une classe existante et l'améliorer en ajoutant des attributs ou des méthodes.
Exemple : Toujours la télévision
Les nouvelles télévisions sont communicantes. Elles disposent d'une interface réseau leur permettant de se connecter à Internet.
Elles disposent également de toutes les caractéristiques des télévisions décrites précédemment.
L'héritage permet de spécialiser la classe CTelevision
en une classe CTeleInter
.
class CTeleInter : public CTelevision {
private :
CComReseau com ; // objet permettant de communiquer par le réseau
} ;
On dira que la classe CTeleInter
est une spécialisation de la classe CTelevision
.
On peut aussi dire que la classe CTelevision
est une généralisation de la classe CTeleInter
.
On dit aussi que CTeleInter
dérive la classe CTelevision
.
Le diagramme ci-contre illustre une généralisation.
\(\sum_a^b\)
Les 3 classes Square
, Circle
, Triangle
ont une relation de généralisation avec la classe Shape
.
La classe Shape
décrira les caractéristiques et comportements communs à toutes les formes.
Chaque classe spécialisée décrira les caractéristiques et comportements spécifiques à une forme.
Mode d'héritage : Les spécificateurs
Dans l'exemple de la télévision, L'héritage est public
. L'autre possibilité est private
.
public
est le spécificateur de l'héritage.
class CTeleInter : public CTelevision {
...
} ;
Classe de base | héritage | héritage |
Membres | accessibles et | accessibles et |
Membres | inaccessibles | inaccessibles |
Membres (voir plus bas) | accessibles et | accessibles et |
Pour comprendre comment lire ce tableau avec les classes CTeleInter
et CTelevision
. 1ère ligne :
avec un spécificateur
public
d'héritage, les membrespublic
deCTelevision
seront accessibles directement à partir deCTeleInter
.
Avec un spécificateur private, les membres
private
deCTelevision
seront accessibles directement à partir deCTeleInter
mais vue comme des membresprivate
. De ce fait, en cas de spécialisation deCTeleInter
, les membrespublic
ne seront pas accessibles à la classe dérivée (tout comme les membresprotected
, ligne 3).
Le spécificateur protected pour un membre d'une classe
Le spécificateur protected
a un sens proche de private
.
Un membre protected
d'une classe est considéré comme private
dans cette classe.
On sait qu'un membre private
d'une classe n'est pas accessible dans la classe dérivée.
L'avantage d'un membre protected
d'une classe de base est qu'il sera accessible dans la classe dérivée comme un membre private
et donc non accessible en cas de nouvelle spécialisation.
Si c'est pas clair, on en discute...
Complément : Représentation UML d'une membre protected
Le symbole #
est utilisé en préfixe de la déclaration du membre de la classe.
L'incidence de l'héritage sur les constructeurs
Les constructeurs et destructeur ne sont pas hérités lors d'une spécialisation.
Dans la classe dérivée, il est nécessaire de définir les nouveaux constructeurs en tenant compte de ceux de la classe de base.
////////////////////////////////////////////
// Fichier cteleinter.h
////////////////////////////////////////////
class CTeleInter : public CTelevision {
private :
CComReseau com ; // objet permettant de communiquer par le réseau
public :
CTeleInter(int noch, int novol) ;
} ;
/////////////////////////////////////////////
// Fichier cteleinter.cpp
/////////////////////////////////////////////
CTeleInter : :CTeleInter(int noch, int novol) : CTelevision(noch, novol) { // CE QUI CHANGE !
...
} // constructeur
Notez l'écriture C++ dans la définition du constructeur.
Un appel est fait au constructeur de la classe de base en lui fournissant les valeurs demandées.
Il ne reste plus qu'à définir l'initialisation spécifique aux objets CTeleInter
.
Classe amie
Il peut s'avérer utile de permettre à une classe dérivée d'accéder aux membres private
de sa classe de base.
L'opérateur friend
permet cela.
Le concepteur de la classe de base doit déclarer explicitement les classes dérivées qui auront le droit d'accéder aux membres private
de la classe de base.
/////////////////////////////////
// fichier ctelevision.h
/////////////////////////////////
class CTelevision {
private :
CTnt *tnt;
...
public :
CTelevision();
CTelevision(int noch, int novol) ;
~CTelevision();
friend class CTeleInter ;
} ; // CTelevision
Les objets CTeleInter
auront ainsi accès aux membres private
de Ctelevision
.
Par contre, l'inaccessibilité reste maintenue pour toute autre classe dérivée de CTelevision
.
Polymorphisme, fonctions virtuelles, classes abstraites
Polymorphisme
Considérez le code source suivant :
class CForme {
public :
void affiche() ;
} // CForme
void CForme : :affiche() {
cout << "Je suis une forme".endl;
} // affiche
class CTriangle : public CForme {
} // CTriangle
class CCercle : public CForme {
} // CCercle
En exécutant le code suivant :
CForme f ;
CCercle c ;
f.affiche() ;
c.affiche() ;
Ces instructions afficheront le même message :
Je suis une forme
Je suis une forme
Cela est dû au fait que la classe CCercle
hérite de la méthode affiche()
de CForme
.
Ce n'est pas forcément ce qu l'on souhaite.
Si nous voulons obtenir un affichage précis correspondant à l'objet en cours, il faudra redéfinir la méthode affiche()
dans chaque classe dérivée.
void CTriangle::affiche() {
cout << "Je suis un triangle" << endl;
} // affiche
void CCercle::affiche() {
cout << "Je suis un cercle" << endl;
} // affiche
L'affichage à présent sera cohérent.
Effectivement, il existe plusieurs fonctions affiche()
, une dans chaque classe.
Il n'y a pas de conflit, c'est le polymorphisme du C++ qui permet ce fonctionnement.
Fonctions virtuelles
Nous voulons stocker plusieurs objets CForme
dans un tableau de la manière suivante :
CForme f ;
CTriangle t ;
CCercle c ;
CForme *fs[3]={&t, &c, &f} ;
Le code ci-dessus crée 2 objets t
et c
, ainsi qu'un tableau de pointeur fs
pour stocker les adresses des objets t
et c
.
Si nous voulons afficher les méthodes affiche()
:
for (int i=0 ; i<2 ; i++) {
fs[i]->affiche() ;
} // for
Et là, problème...L'affichage est le suivant :
Je suis une forme
Je suis une forme
Je suis une forme
La tableau étant déclaré de type CForme
, c'est une référence à la méthode affiche()
de cette classe qui est invoquée.
Pour que cela fonctionne, il est nécessaire de déclarer la méthode affiche()
comme virtual
dans la classe de base et dans les classes dérivées.
virtual void affiche() ;
Dans la définition de la méthode (fichier .cpp
) :
virtual void CForme::affiche() {
...
} //affiche
Les affichages seront à présent les bons.
Dans les classes dérivées de CForme
, les méthodes affiche()
seront également virtual.
Fondamental : Méthode virtuelle
La méthode affiche()
examinée plus haut est virtuelle.
Elle est définit dans sa classe de base et éventuellement dans les classes dérivée.
Grâce au polymorphisme (plusieurs formes), chaque classe dérivée peut utiliser ou pas une spécialisation de cette méthode.
Fonction virtuelle pure et classe abstraite
Les fonctions virtuelles pures définissent des comportements qui devront impérativement être définis dans les classes dérivées.
La syntaxe de déclaration dans la classe est la suivante :
class CVehicule {
virtual void dessiner()=0; // syntaxe d'une méthode virtuelle pure
} // CVehicule
Cette classe laisse entendre qu'il sera nécessaire de dessiner le véhicule mais cela n'est pas possible pour le moment car cela dépend du type de véhicule, donc d'une spécialisation de cette classe.
Définition : Méthode virtuelle pure ?
C'est une méthode dont le code n'est pas défini. Elle se déclare dans le corps de la classe précédée du mot clef virtual
.
La fonction est initialisée à 0.
Définition : Classe abstraite ?
C'est une classe qui contient au moins une méthode virtuelle.
Attention : Instancier une classe abstraite
Il n'est pas possible de créer un objet à partir d'une classe abstraite.
L'instanciation n'est pas possible de fait qu'au moins une méthode n'est pas définie.
La classe doit obligatoirement être dérivée et ses fonctions virtuelles définies.