Mandragor


L'héritage

Introduction à la notion d'héritage.

L'heritage est un concept propre au langage objet qui consiste à dire qu'un objet de type B est en fait un objet de type A et un peu plus. L'intérêt de l'héritage est qu'une classe derivée (ou encore classe enfant) possédera les mêmes attributs et fonctions membres que la classe dont il dérive. Cela rend le code plus court, utilise moins de place en mémoire, et donc rend l'exécution du programme plus rapide.

De plus, l'héritage permet l'inclusion de nouveaux concepts ultra-importants comme le polymorphisme. D'un point de vue plus pratique, cela évite d'avoir à faire 30 copier/coller si l'on a oublié une variable dans ce qui serait la classe de base... :)

L'héritage en C++.

Maintenant que vous savez à quoi sert l'héritage, voyons un peu comment il faut écrire cela en C++. Les modifications de programme concernant uniquement les classes dérivées, elles seront declarées diferemment; on fera:

  class <classe derive> : <etiquette de protection> <classe mere>

A partir de là, la classe dérivée possédera tous les attributs de la class mère. Attention par contre à leur niveau de protection qui dépend de l'étiquette utilisée au moment de la declaration. (Le sujet est expliqué plus loin).

Exemple rapide.

Considérez le programe suivant:

  class salle_de_bain:public piece
  {
    bool douche;
    bool baignoire;
    bool WC;
    //et je sais pas koi plus l'encapsulation...
  };
  class cuisine:public piece
  {
    bool cuisiniereagaz;
    bool cuisiniereelectrique;
    //et je sais pas koi plus l'encapsulation...
  };

Il parait évident que l'héritage dans ce cas est extrêmement utile. En effet, supposons maintenant que l'on ait à faire une classe cuisine. alors on gagnerait énormément de lignes car on n'aurait plus a redéclarer les variable "communes". On ne parle ici même pas des problèmes que pourraient poser d'éventuelles modifications.

Etiquettes de protection et héritage

Expliquons le mot cle protected

Quand une variable membre est en protected, elle est pour cette classe considérée comme private, c'est à dire que seules les fonctions membres auront accés a cette variable (fonction).

Mais les classes dérivées y auront accés comme si elle etait public (les fonctions extérieures ne pourront quand même pas accéder a cette variable/fonction). Si une variable était en public dans la classe mère, alors elle serait accessible comme si elle etait en public dans la classe fille. Si une variable était en private dans la classe mère alors elle ne serait pas visible de la classe fille.

On voit ici toute l'importance du choix des étiquettes lors de la création de la classe mere.

Il existe aussi une spécification d'héritage, c'est le class salle_de_bain:public piece. En effet, ce public là a aussi une grande importance... Il permet de faire comme si les membre de la classe de base avaient une autre étiquette... Je m'explique: si la spécification d'héritage est publique, alors les étiquettes demeurent inchangées.
Si la spécification d'héritage est protected, alors les étiquettes public deviennent protected.
Si la spécification d'heritage est private, alors tout les membres deviennent private.

Cette spécification d'héritage n'est utile que pour les classes dérivées de la classe fille, les accès de la classe fille ne sont en aucun cas modifiés... Pour éclairer ces propos, considérons le programme commenté suivant:

  class A
  {
    public:
      int a;
    protected:
      int b;
    private:
      int c;
  };

  class B:private A
  {
    // B a accès aux variables a et b de A
  };

  class C:public B
  {
    // C n'a accès à aucune des variables de A, mais aux variables
    // public et protected de B.
  };

  class D:protected A
  {
    // D a accès comme B au variables a et b de A.
  };

  class E:public D
  {
    // E a accès à a et b de A ainsi qu'à toutes les variables non
    // private de D. Par contre, le programme exterieur n'aura
    // accès à aucune des variable de A.
  };

Les constructeurs / destructeurs et l'Heritage

Les constructeur sont executés dans le sens logique (de mère en fille). Pour les destructeurs, c'est le même principe, on detruit de fille en mère. Mais comment faire si le constructeur de A avait des arguments? Et bien on lui passera au moment de la declaration du constructeur! On fera:

  B::B(int a, int b, int c) // Supposons que B derive de A
    :A(int a, int b) // Ici on passe les paramètres au constructeur de A
  {
      ...
  }

Ainsi le constructeur de B apellera le constructeur de A avec comme paramètres a et b.

Héritage multiple.

En C++, on considère qu'un objet peut tirer ses caractéristiques de plusieurs objets differents. Par exemple, (et là je parle en connaissance de cause...) un lance-roquette est une arme à distance ET un explosif... (non, non je ne fais pas des attentats, mais j'ai eu a simuler ce problème...), donc comment faire... Et bien c'est facile, on fait une liste d'héritage et comme toute liste, on utilise la virgule pour séparer deux element.

  class lance-roquette: public explosif, public distance
  {
    ...
  }

La classe lance-roquette a donc accès aux données membres de la classe explosif et à celles de distance. Un autre problème survient alors... Considérez le programme suivant...

  class A
  {
    ...
  };
  class B:public A
  {
    ...
  };
  class C:public A
  {
    ...
  };
  class D:public B, public C
  {
    ...
  };

Quand vous déclarerez un objet de type D, alors le compilateur va faire appel au constructeur de B qui va créer un objet A, puis un appel au constructeur de C qui va créer un objet de type A. Et là ca ne va plus... Quand vous accéderez à un élément de A à partir de la classe D, quel objet mère va-t-il considérer? Et bien c'est facile, comme il ne sait pas, le compilateur va générer une erreur... La question est: comment remédier à cela?

On va pour éviter cela utiliser le mot-clé virtual avant de dériver les classe B et C. On aura donc:

  class A
  {
    ...
  };
  class B:virtual public A
  {
    ...
  };
  class C:virtual public A
  {
    ...
  };
  class D:public B, public C
  {
    ...
  };

Ainsi le compilateur ne créera qu'un seul objet de type A par objet de type D. Une dernière chose... En cas d'héritage multiple avec des constructeur à arguments, comme pour l'héritage multiple on utilise une liste.. la classe D aurait un constructeur de ce type:

  D::D(int a, int b, int c, int d):A(int a), B (int b), C (int c)
  {
      ...
  }