User Tools

Site Tools


in204:cpp:syntax:class:deriving

This is an old revision of the document!


Classe dérivée & héritage

Pourquoi hériter d'une classe

Supposons que nous avons réaliser un objet qui effectuer une certaine tâche de base.

class enumerate_characters
{
private:
  int mPosition;
  int mNumberOfCharacters;
  const char* mString;
public:
  enumerate_characters(const char* aString, int theNumberOfCharacters):
      mString(aString), mNumberOfCharacters(theNumberOfCharacters) {}
 
  int next()
  {
      return mPosition < mNumberOfCharacters ? 
        (int)mString[mPosition++] : -1;
  }
  void reset()
  {
      mPosition = 0;
  }
}

Cette classe énumère les caractère de la chaîne de caractère passée en paramètre en commençant par le premier caractère et en terminant par le dernier caractère.

Supposant que je ne souhaite plus énumérer les caractères en commençant par le premier caractère et en terminant par le dernier caractère mais à l'inverse en commençant par le dernier caractère et en terminant par le premier. Je devrais ajouter à cette classe la fonction previous() par exemple. Je peux soit ajouter cette méthode dans la classe ou au contraire ajouter cette méthode à une extension de la classe, ce que l'on appelle une classe dérivée ou une classe fille qui hérite de son parent. L'intérêt est dans ce cas que je vais pouvoir modifier le comportement de la classe.

class enumerate_characters
{
private:
  int mPosition;
protected:
  int mNumberOfCharacters;
  const char* mString;
public:
  enumerate_characters(const char* aString, int theNumberOfCharacters):
      mString(aString), mNumberOfCharacters(theNumberOfCharacters) {}
 
  int next()
  {
      return mPosition < mNumberOfCharacters ? 
        (int)mString[mPosition++] : -1;
  }
  void reset()
  {
      mPosition = 0;
  }
}
 
class bidi_enumerate_characters: enumerate_characters
{
private:
  int mReversePosition;
public:
  bidi_enumerate_characters(const char* aString, int theNumberOfCharacters):
      enumerate_characters(aString, theNumberOfCharacters), mReversePosition(theNumberOfCharacters) {}
 
  int previous()
  {
      return mReversePosition >= 0 ? 
        (int)mString[mPosition++] : -1;
  }
  void reset()
  {
      mReversePosition= mNumberOfCharacters;
      enumerate_characters()::reset();
  }
}

La classe bidi_enumerate_characters est une extension de la classe enumerate_characters :

class bidi_enumerate_characters: enumerate_characters
{

ce qui veut que l'ensemble des champs et méthodes présents dans la classe enumerate_characters sont présents dans la classe bidi_enumerate_characters. Attention, les champs ou méthodes privées sont présentes mais ne sont pas accessibles. Ici, nous avons besoin des champs mNumberOfCharacters et mString dans la classe dérivée, c'est pour cela que nous avons modifié la classe de base enumerate_characters pour les rendre toujours inaccessibles en dehors de la classe mais accessible dans les classes derivées en les déclarant comme proctected :

class enumerate_characters
{
private:
  int mPosition;
protected:
  int mNumberOfCharacters;
  const char* mString;
public:

Nous voyons que la classe bidi_enumerate_characters ajoute la fonction previous() à la classe de base.

  int previous()
  {
      return mReversePosition >= 0 ? 
        (int)mString[mPosition++] : -1;
  }

c'est pour cela qu'on dit qu'une classe dérivée est une extension de la classe de base par ce qu'elle peut ajouter des fonctions et des comportements.

Ensuite, nous constatons que la classe bidi_enumerate_characters rédéfinit la méthode reset() :

  void reset()
  {
      mReversePosition= mNumberOfCharacters;
      enumerate_characters()::reset();
  }

En effet, lorsque nous appellons la méthode reset(), nous mettons d'abord le champs mReversePosition à mNumberOfCharacters et puis ensuite nous appellons la méthode reset() de la classe de base enumerate_characters()::reset(). Nous avons spécialisé le comportement de la fonction reset() par rapport à la fonction reset() de la classe de base. C'est pour cela que les classes dérivées sont aussi appellées classes spécialisées.

Définition d'une classe dérivée

La classe dérivée est une extension d'une classe de base. C'est une classe à laquelle on a ajouté des champs et des fonctions membres supplémentaires.

La classe dérivée hérite de l'ensemble des fonctions membres et des champs définis par la ou les classes de bases. C'est pour cela que l'on parle d'héritage quand on parle de classes dérivées d'une autre classe.

Déclaration d'une classe dérivée

La syntaxe pour déclarer une classe comme héritant d'une classe de base est la suivante :

class DerivingClass: public BaseClass
{
};

Accessibilité des méthodes et des champs d'une classe de base

Cette définition indique que la classe DerivingClass hérite de la classe BaseClass et donc que l'ensemble des méthodes et des champs définis par la classe BaseClass sont présents dans la classe DerivingClass. Cependant, il faut faire attention, présent ne veut pas dire qu'ils sont accessibles dans la classe de base. Ainsi, nous avons les règles d'accessibilité suivantes pour les méthodes et les champs définis dans la classe de base.

Déclaration dans la classe de base Accesibilité dans la classe dérivée
les champs ou méthodes déclarés public dans la classe de base les champs et méthodes sont accessibles dans la classe dérivée
les champs ou méthodes déclarés protected dans la classe de base les champs et méthodes sont accessibles dans la classe dérivée
les champs ou méthodes déclarés private dans la classe de base les champs et méthodes ne sont pas accessibles dans la classe dérivée

Cependant, il faut aussi s'intéresser à l'accesibilité des méthodes en dehors de la classe dérivée. C'est ici qu'intervient les attributs d'accessibilité que l'on place devant le type de la classe dont hérite qui peuvent être public, protected ou private.

Attribut d'héritage Accessibilité en dehors de la classe Accessibilité dans une classe dérivant de DerivingClass
DerivingClass: public BaseClass Les méthodes et champs public de BaseClass sont accessibles. Les méthodes et champs public ou protected de BaseClass sont accessibles à la classe dérivée.
DerivingClass: private BaseClass Aucune méthode ou champs de BaseClass ne sont accessibles. Aucune méthode ou champs de BaseClass ne sont accessibles à la classe dérivée.
DerivingClass: protected BaseClass Aucune méthode ou champs de BaseClass sont accessibles. Les méthodes et champs public ou protected de BaseClass sont accessibles à la classe dérivée.

Déclarer des champs dans une classe dérivée

Les champs qui sont déclarés dans une classe dérivée ne peuvent pas avoir le même nom que les champs dans une des classes de base.

Déclarer des méthodes dans une classe dérivée

Cas 1: La méthode method a un nom différent de celui des méthodes existance dans la classe de base.

C'est par exemple le cas de la méthode previous() dans la classe bidi_enumerate_characters :

  int previous()
  {
      return mReversePosition >= 0 ? 
        (int)mString[mPosition++] : -1;
  }

Ce sont les règles habituelles de la déclaration des méthodes dans les classes. La nouvelle méthode s'ajoute aux anciennes méthodes.

Cas 2: La méthode method a le même nom que celui des méthodes existance dans la classe de base.

C'est par exemple le cas de la méthode reset() dans la classe bidi_enumerate_characters :

  void reset()
  {
      mPosition = 0;
      enumerate_characters()::reset();
  }

Règle 1: Si nous définissons une ou plusieurs méthodes ayant le nom method et que des méthodes ayant le même nom ont été définies dans la classe de base, les méthodes ayant comme nom method sont cachées dans la classe de base.

class enumerate_characters
{
private:
  int mPosition;
protected:
  int mNumberOfCharacters;
  const char* mString;
public:
  enumerate_characters(const char* aString, int theNumberOfCharacters):
      mString(aString), mNumberOfCharacters(theNumberOfCharacters) {}
 
  int next()
  {
      return mPosition < mNumberOfCharacters ? 
        (int)mString[mPosition++] : -1;
  }
  void reset()
  {
      mPosition = 0;
  }
}
 
class extended_enumerate_characters: enumerate_characters
{
public:
  extended_enumerate_characters(const char* aString, int theNumberOfCharacters):
      enumerate_characters(aString, theNumberOfCharacters) {}
 
  int next(int lookup)
  {
      mPosition += increment;
      if(mPosition >= mNumberOfCharacters)
      {
          mPosition = mNumberOfCharacters;
          return (int)-1;
      }
      return (int)mString[mPosition++];
  }
}

Dans ce cas, la méthode next(int) va être la seule méthode visible dans la classe extended_enumerate_characters. La méthode next() de la classe enumerate_characters mais elle sera masquée et sera donc inaccessible.

Si nous voulons rendre la méthode next() visible, il faudra ajouter à la classe extended_enumerate_characters la directive : using enumerate_characters::next pour indiquer que les méthodes ayant pour nom next et étant définie dans la classe de base sont aussi visible dans la classe dérivée.

class extended_enumerate_characters: enumerate_characters
{
public:
  extended_enumerate_characters(const char* aString, int theNumberOfCharacters):
      enumerate_characters(aString, theNumberOfCharacters) {}
 
  using enumerate_characters::next;
  int next(int lookup)
  {
      mPosition += increment;
      if(mPosition >= mNumberOfCharacters)
      {
          mPosition = mNumberOfCharacters;
          return (int)-1;
      }
      return (int)mString[mPosition++];
  }
}

Dans la classe extended_enumerate_characters, les deux méthodes enumerate_characters::next() et extended_enumerate_characters::next(int) sont accessibles.

    extended_enumerate_characters enumerator("abcdefg");
    std::cout << enumerator.next(2);
    std::cout << enumerator.next();
in204/cpp/syntax/class/deriving.1616927270.txt.gz · Last modified: 2021/03/28 10:27 by bmonsuez