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.

ExempleToujours 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.

1
class CTeleInter : public CTelevision {
2
   private :
3
    CComReseau com ; // objet permettant de communiquer par le réseau
4
} ;

Cette classe CTeleInter hérite de la classe CTelevision.

La représentation UML/SysML est exprimée ci-contre.

Il faut comprendre que CTeleInter est une sorte de CTelevision.

Héritage (formalisme UML/SysML)
Héritage (formalisme UML/SysML)
Spécialisation / Généralisation
Spécialisation / Généralisation

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.

1
class CTeleInter : public CTelevision {
2
...
3
} ;
Les spécificateurs lors de l'héritage

Classe de base

héritage public (classe dérivée)

héritage private (classe dérivée)

Membres public

accessibles et public

accessibles et private

Membres private

inaccessibles

inaccessibles

Membres protected

(voir plus bas)

accessibles et protected

accessibles et private

Pour comprendre comment lire ce tableau avec les classes CTeleInter et CTelevision. 1ère ligne :

  • avec un spécificateur public d'héritage, les membres public de CTelevision seront accessibles directement à partir de CTeleInter.

  • Avec un spécificateur private, les membres private de CTelevision seront accessibles directement à partir de CTeleInter mais vue comme des membres private. De ce fait, en cas de spécialisation de CTeleInter, les membres public ne seront pas accessibles à la classe dérivée (tout comme les membres protected, 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émentRepré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.

1
////////////////////////////////////////////
2
// Fichier cteleinter.h
3
////////////////////////////////////////////
4
#include "ctelevision.h"
5
class CTeleInter : public CTelevision {
6
   private :
7
    CComReseau com ; // objet permettant de communiquer par le réseau
8
  public :
9
    CTeleInter(int noch, int novol) ;
10
} ;
11
 
12
/////////////////////////////////////////////
13
// Fichier cteleinter.cpp
14
/////////////////////////////////////////////
15
#include "cteleinter.h"
16
CTeleInter : :CTeleInter(int noch, int novol) : CTelevision(noch, novol) { // CE QUI CHANGE !
17
...
18
} // 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.

1
/////////////////////////////////
2
// fichier ctelevision.h
3
/////////////////////////////////
4
class CTelevision {
5
private :
6
   CTnt *tnt;
7
...
8
public :
9
  CTelevision();
10
  CTelevision(int noch, int novol) ;
11
  ~CTelevision();
12
friend class CTeleInter ;
13
} ; // 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 :

1
class CForme {
2
   public :
3
    void affiche() ;
4
} // CForme
5
 
6
void CForme : :affiche() {
7
  cout << "Je suis une forme".endl;
8
} // affiche 
9
class CTriangle : public CForme {
10
} // CTriangle
11
 
12
class CCercle : public CForme {
13
} // CCercle

En exécutant le code suivant :

1
CForme f ;
2
CCercle c ;
3
f.affiche() ;
4
c.affiche() ;

Ces instructions afficheront le même message :

1
Je suis une forme
2
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.

1
void CTriangle::affiche() {
2
  cout << "Je suis un triangle" << endl;
3
} // affiche 
4
void CCercle::affiche() {
5
  cout << "Je suis un cercle" << endl;
6
} // 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 :

1
CForme f ;
2
CTriangle t ;
3
CCercle c ;
4
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() :

1
for (int i=0 ; i<2 ; i++) {
2
  fs[i]->affiche() ;
3
} // for

Et là, problème...L'affichage est le suivant :

1
Je suis une forme
2
Je suis une forme
3
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.

1
virtual void affiche() ;

Dans la définition de la méthode (fichier .cpp) :

1
virtual void CForme::affiche() {
2
...
3
} //affiche

Les affichages seront à présent les bons.

Dans les classes dérivées de CForme, les méthodes affiche() seront également virtual.

FondamentalMé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 :

1
class CVehicule {
2
  virtual void dessiner()=0; // syntaxe d'une méthode virtuelle pure
3
} // 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éfinitionMé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éfinitionClasse abstraite ?

C'est une classe qui contient au moins une méthode virtuelle.

AttentionInstancier 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.