Utilité
Nous avons acquis
dans le chapitre précédent, la notion d'héritage.
Elle nous permet en outre de créer de véritables arbres de
classes. Reprenons notre exemple de Point et de PointCol.
Nous avons implémenté des méthodes qui permettent
l'affichage, ou encore l'initalisation des données, dans chacune
des deux classes : Affiche
dans Point, AfficheCol
dans PointCol par exemple.
Je suppose que vous vous
êtes demandé pourquoi nous ne leur avons pas donné
le même nom ! Le mieux pour le comprendre est d'essayer.
| #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
char, unsigned
char, unsigned
char );
void
Affiche();
};
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::Affiche()
{
cout << "Point (" << x
<< ", " << y << ")";
cout << "de couleur : RGB(" <<
(int)byRed <<
"," << (int)byGreen;
cout << "," << (int)byBlue
<< ")." << endl;
} |
Cette déclaration
de la classe PointCol à l'exécution, donne les résultats
voulus, à savoir que c'est la bonne méthode Affiche
qui est appelée. En fait, la liaison est établie statiquement
à la compilation. Ici, le compilateur sait très bien quelle
fonction membre utiliser. Mais maintenant, imaginons l'utilisation suivante
:
void main()
{
PointCol ptc(5,10, 50,150,200);
ptc.Affiche();
Point pt(52,17);
pt.Affiche();
Point *ppt; //
Pointeur de point
ppt = &ptc; //
le pointeur pointe désormais sur un point coloré : légal
!
ppt->Affiche();
} |
Le résultat peut vous
sembler surprenant. En fait, la typage étant effectué statiquement,
pour le compilateur, ppt
reste quoi qu'il advienne un pointeur sur Point.
Or, nous n'avions pas vu
cela encore, mais il est possible d'affecter une adresse de classe fille
à un pointeur de classe de base...
Dans ce cas, vu que l'affectation
est dynamique, un appel de la méthode Affiche
utilise en fait la déclaration de la classe de base, Point.
Un autre exemple pour illustrer
l'utilité des fonctions virtuelles, consisterait à réaliser
un affichage "descendant". Il s'agit de réaliser un affichage de
toutes les informations relatives aux deux classes, Point et PointCol,
en appelant une seule méthode de Point. Vous comprendrez
mieux cela, en étudiant le code :
// Point.h
class Point
{
int x;
int y;
public :
Point(int
a, int b){ x=a;
y=b; }
void
Init(int a, int
b){ x=a; y=b; }
void
Deplace(int a,
int
b){ x+=a; y+=b; }
void
Affiche();
void
AfficheTout();
};
// Point.cpp
void Point::Affiche()
{
cout << this << "->" <<
x << ", " << y << endl;
}
void Point::AfficheTout()
{
cout << this
<< "->" << x << ", " << y << endl;
Affiche();
} |
| #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
char, unsigned
char, unsigned
char );
void
Affiche();
};
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::Affiche()
{
cout << "Couleur : RGB(" <<
(int)byRed <<
"," << (int)byGreen;
cout << "," << (int)byBlue
<< ")." << endl;
} |
En appelant AfficheTout
dans le programme, nous souhaitons afficher les renseignements de la classe
Point (code contenu dans la première ligne de la méthode),
mais aussi ceux de la classe appelante. Par exemple :
void main()
{
PointCol ptc(5,10, 50,150,200);
ptc.Affiche();
Point pt(52,17);
pt.Affiche();
ptc.AfficheTout();
} |
Dans ce cas précis,
nous affichons les informations de couleurs de ptc,
puis de coordonnées de pt.
La dernière ligne devrait afficher les informations de coordonnées
et de couleurs de ptc.
Mais de ce premier test, il n'en est rien !
Mécanisme
Dans le paragraphe
précédent, nous avons vu que dans certaines conditions, donner
le même nom à une méthode de la classe fille qu'une
méthode de la classe de base, peut être source d'erreur, ou
plutôt, d'incompréhension.
Pour éviter cela,
il faudrait pouvoir indiquer au compilateur de lier certaines méthodes
dynamiquement. En effet, il y a des cas où, seulement à
l'exécution, le programme peut savoir quelle fonction membre employer.
Les fonctions virtuelles servent à celà. Soit la définition
suivante :
// Point.h
class Point
{
int x;
int y;
public :
Point(int
a, int b){ x=a;
y=b; }
void
Init(int a, int
b){ x=a; y=b; }
void
Deplace(int a,
int
b){ x+=a; y+=b; }
virtual
void Affiche();
void
AfficheTout();
};
// Point.cpp
void Point::Affiche()
{
cout << this << "->" <<
x << ", " << y << endl;
}
void Point::AfficheTout()
{
cout << this
<< "->" << x << ", " << y << endl;
Affiche();
} |
Essayez maintenant les deux
tests précédemment implémentés. Vous constaterez
que grâce à ce virtual,
la liaison est désormais dynamique et donc, que la bonne
méthode est appelée.
|