MekkiSoft Formation Gratuite C++

Fonctions Virtuelles

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.


Cours précédent Cours précédent
Cours suivant Cours suivant