samedi 20 octobre 2012

L’Héritage en C++


Définition et mise en oeuvre

Définition

Nous avons vu dans le premier chapitre que l’héritage est en C++, et plus généralement en P.O.O., un concept fondamental. En effet, il permet de définir une nouvelle classe "fille", qui héritera des caractéristiques de la classe de base. Le terme "caractéristiques" inclut en fait l’ensemble de la définition de cette classe mère.
D’un point de vue pratique, il faut savoir qu’il n’est pas nécessaire à la classe fille de connaître l’implémentation de la base : sa définition suffit. Ceci sera mis en application. De plus, on peut hériter plusieurs fois de la même classe, et une classe fille pourra également servir de base pour une autre. Il est alors possible de décrire un véritable "arbre d’héritage".

Mise en œuvre

Mettre en oeuvre la technique de l’héritage est assez en simple en C++. Le plus difficile reste en fait la conception, qui nécessite un gros travail afin de bien séparer les différentes classes.
Le premier exemple qui nous permettra de réaliser notre premier héritage, propose de définir une classe PointCol qui hérite de Point. Sémantiquement parlant, PointCol est un Point auquel on rajoute la gestion de la couleur. Nous obtenons alors :
class PointCol : public Point
{
 unsigned char byRed;   // La composante rouge 
 unsigned char byGreen; // La composante verte 
 unsigned char byBlue;  // La composante bleue 
public :
 // Coloration d’un point 
 void Colore(unsigned char R,unsigned char G,unsigned char B )
 {
    byRed = R;
    byGreen = G;
    byBlue = B;
 }
};
Vous pouvez remarquer la notation ": public Point". Ceci signifie que notre point coloré hérite publiquement de Point, c’est-à-dire que tous les membres publics de Point seront membres publics de PointCol.
En déclarant un objet de type PointCol, il est ainsi possible d’accéder aux membres publics de PointCol, donc, mais également de Point. C’est une notion essentielle de la P.O.O. !
Pour mettre en application notre exemple, nous allons utiliser la classe Point qui suit. Nous allons pour la première fois faire cela dans les "règles de l’art", en séparant physiquement la définition de l’implémentation (un fichier ".h" et un fichier ".cpp").
Fichier Point.h :
class Point
{
 int x;
 int y;
public :
 Point(){}
 void Init(int a, int b){ x=a; y=b; }
 void Deplace(int a, int b){ x+=a; y+=b; }
 void Affiche();
};
Fichier Point.cpp :
#include <iostream.h>
#include "Point.h"
void Point::Affiche()
{
 cout << this << "->" << x << ", " << y << endl;
}
Vous pouvez maintenant rajouter le fichier main.cpp suivant :
#include "Point.h"
class PointCol : public Point
{
 unsigned char byRed;   // La composante rouge 
 unsigned char byGreen; // La composante verte 
 unsigned char byBlue;  // La composante bleue 
public :
 // Coloration d’un point 
 void Colore(unsigned char R, unsigned char G, unsigned char B)
 {
  byRed = R;
  byGreen = G;
  byBlue = B;
 }
};
void main()
{
 PointCol ptc;
 ptc.Init( 5, 10 );
 ptc.Colore( 64, 128, 192 );
 ptc.Affiche();
 ptc.Deplace( 3, 6 );
 ptc.Affiche();
}

Utilisation des membres de la classe de base

Utiliser des membres de la classe de base est simple à réaliser. Il faut cependant faire attention à leur statut. Les membres privés ne peuvent être appelés. Soit une méthode "InitCol" qui initialise un point coloré :
void InitCol( int Abs, int Ord,
            unsigned char R, unsigned char G, unsigned char B )
{
 Point::Init(Abs, Ord);
 byRed = R;
 byGreen = G;
 byBlue = B;
}
Il suffit donc d’appeler la méthode souhaitée, précédée de la classe.

Redéfinition des fonctions membre et appel des constructeurs

L’appel à la méthode "Affiche" fonctionne très bien, et utilise en fait la déclaration faite dans la classe Point, ce qui est logique puisque PointCol n’en possède aucune autre. Mais que se passe-t-il si on veut afficher un point coloré ?
Une première solution consiste à introduire une nouvelle méthode "AfficheCol" dans la classe fille.
En plus de cette méthode, nous allons ajouter un constructeur qui permet l’initialisation de la classe PointCol. Vous voyez immédiatement quelle pourrait être la définition :
PointCol( int Abs, int Ord, unsigned char R, unsigned char G,
          unsigned char B); 
Il contient donc les coordonnées du point, plus les composantes de la couleur. La mise en oeuvre est un peu plus complexe. Soit notre classe Point :
class Point
{
 int x;
 int y;
public :
 Point(intint);
 void Deplace(int a, int b){ x+=a; y+=b; }
 void Affiche();
};
Nous voyons bien que, quelque part, il faudrait passer les coordonnées entrées en paramètres du constructeur initialisant de PointCol, vers celui de Point ! Ceci s’effectue de la façon suivante :
#include "Point.h"
class PointCol : public Point
{
 unsigned char byRed;
 unsigned char byGreen;
 unsigned char byBlue;
public :
 PointCol(int,int,unsigned char,unsigned char,unsigned char);
 void Colore( unsigned charunsigned charunsigned char );
};
// Constructeur initialisant de la classe PointCol, 
// faisant appel au constructeur initialisant de Point. 
PointCol::PointCol( int Abs, int Ord, unsigned char R, unsigned char G, unsigned char B) : Point(Abs, Ord)
{
 byRed = R;
 byGreen = G;
 byBlue = B;
}
void PointCol::Colore( unsigned char R,
                       unsigned char G, unsigned char B )
{
 byRed = R;
 byGreen = G;
 byBlue = B;
Remarquez la transmission de paramètres effectuée par le ":Point(Abs, Ord)". C’est en fait un appel au constructeur initialisant de la classe de base.
Il est possible d’étendre cette mise en oeuvre à tous les constructeurs, par exemple par recopie. Essayez ! Vous pouvez également changer le type d’héritage et le rendre "private", pour voir la différence.
Dans certains cas, il peut être intéressant de pouvoir avoir accès aux données membres de la classe de base. Par exemple, reprenons notre affichage dans PointCol :
#include "Point.h"
class PointCol : public Point
{
 unsigned char byRed;
 unsigned char byGreen;
 unsigned char byBlue;
public :
 PointCol(int,int,unsigned char,unsigned char,unsigned char);
 void Colore( unsigned charunsigned charunsigned char );
 void AfficheCol();
};
PointCol::PointCol( int Abs, int Ord, unsigned char R, unsigned char G, unsigned char B) : Point(Abs, Ord)
{
 byRed = R;
 byGreen = G;
 byBlue = B;
}
void PointCol::Colore( unsigned char R, unsigned char G, unsigned char B )
{
 byRed = R;
 byGreen = G;
 byBlue = B;
}
void PointCol::AfficheCol()
{
 Point::Affiche();
 // Notez le "cast" en "int" des composantes nécessaire, sinon 
 // le compilateur prend les valeurs (char) pour des caractères
 cout << "Couleur : RGB(" << (int)byRed << "," << (int)byGreen;
 cout << "," << (int)byBlue << ")." << endl;
}
Le résultat est satisfaisant, mais un appel à la méthode Affiche de Point est peut-être fastidieux, d’autant plus qu’il pourrait être intéressant d’avoir accès aux coordonnées du point, directement[5].
Ceci n’est pas possible ! Si vous essayez, le compilateur vous donnera une erreur de type : cannot access private member declared in class ‘Point’. Ceci vient du fait que les membres x et y de Point sont privés.

"Statuts" de dérivation

La solution à ce problème permet de laisser les membres inaccessibles aux utilisateurs de la classe, mais pas des classes qui en héritent. Il suffit de remplacer le "private" par "protected". Notre classe Point devient alors :
class Point
{
protected :
 int x;
 int y;
public :
 Point(int abs, int ord){ x=abs; y=ord; }
 void Deplace(int a, int b){ x+=a; y+=b; }
 void Affiche();
};
Et notre méthode AfficheCol :
void PointCol::AfficheCol()
{
 cout << "Point (" << x << ", "<< y << ") ";
 cout << "de couleur : RGB("<<(int)byRed<<","<<(int)byGreen;
 cout << "," << (int)byBlue << ")." << endl;
}
L’intérêt du statut protégé est donc double puisque les données se retrouvent inaccessibles pour l’extérieur, ce qui préserve l’encapsulation des données de la classe, mais par contre demeurent toujours utilisables pour d’éventuels héritages.

Notion d’héritage : élargissement

Il est possible d’étendre la notion d’héritage à plusieurs classes : un véritable arbre peut-être créé, par exemple une classe C qui hériterait de A et B (héritage multiple). Nous n’aborderons pas ces fonctionnalités dans ce tutorial, mais vous pouvez consulter un livre plus complet qui abordera certainement cela.

Aucun commentaire:

Enregistrer un commentaire