Reprenons l'exemple des classes point et emplacement avec une nouvelle classe cercle qui hérite de point. Ces classes sont manipulés à l'aide de pointeurs.
...
class cercle : public point {
int rayon ;
public:
cercle(const int rr=20,const int coul=1,
const int xx=0, const int yy=0) ;
void affiche(void) const ;
void changeRayon(const int rr) ;
} ;
inline cercle::cercle(const int rr, const int coul,
const int xx, const int yy) :
rayon(r),point(coul,xx,yy) {}
void cercle::affiche(void) {
cout << "cercle" << endl ;
}
inline void cercle::changeRayon(const int rr) { rayon=rr ; }
int main(void) {
point *ptr_point ;
cercle * ptr_cercle ;
ptr_point = new point (3,5,5) ;
ptr_cercle = new cercle (50,3,100,100) ;
ptr_point->affiche() ; // affiche un point
ptr_cercle->affiche() ; // affiche un cercle
ptr_cercle->point::affiche() ; // affiche un point
// (pas de sens)
ptr_cercle->changeCouleur(5) ; // ok
ptr_point->changeRayon(10) ; // erreur
delete ptr_point ;
delete ptr_cercle ;
ptr_cercle = new point ; // erreur de compilation
ptr_point = new cercle ; // ok
ptr_point->changeCouleur(2) ; //ok
ptr_point->affiche() ; // affiche un point
ptr_point->changeRayon(30) ; //erreur
...
Une variable pointeur d'une sur-classe peut pointer sur une sous-classe
(l'inverse est faux). Dans ce cas, lors d'un appel à un membre
défini dans les deux classes, le compilateur repose sur un mécanisme
de résolution statique : c'est le type de la variable pointeur donné à
déclaration de la variable qui permet de sélectionner le membre (celui
de la super-classe). Dans notre cas, il serait interressant d'avoir un
mécanisme de résolution dynamique, ce qui permettrait d'executer le
membre de la classe qui a été alloué. Ce mécanisme est possible
en C++ par l'introduction du mot-clé virtual.
class point {
...
virtual void affiche(void) const ;
} ;
...
int main(void) {
point *ptr_point = new cercle(5,5,100,100) ;
ptr_point->affiche() ; // affiche un cercle
...
le passage de paramètre par valeur impose le mécanisme de résolution
statique.
void fonction(point p) {
p.affiche() ;
}
int main(void) {
point p ;
cercle c ;
fonction(p) ; // affiche un point
fonction(c) ; // affiche un point
L'utilisation de réference au passage de paramètre résoud le problème
(il faut bien sur que point::affiche soit déclarée virtuelle).
Le mécanisme de résolution tardive ou dynamique est largement
utilisée dans la conception de programmes orienté objet. Une nouvelle
notion appara^t : celle de classe abstraite. C'est une classe
dont tous les membres sont virtuels et non définis. Cette classe n'a pas de
sens dans la vie réelle , seules les classes dérivés de celle-ci en ont
une, comme dans l'exemple ci-après :
class figure : public emplacement {
int couleur ;
public:
figure(const int coul=1,const int xx=0,const int yy=0) ;
virtual void affiche(void) const = 0 ;
void changeCouleur(const int c) ;
} ;
class point : public figure {
public:
point(const int coul=1,const int xx=5, const int yy=5) ;
void affiche(void) const ;
} ;
class cercle : public figure {
...
void affiche(void) const ;
} ;
...
int main(void) {
figure *pfig ;
int reponse ;
cout << "voulez-vous un cercle (1) ou un point (2) ;
cin >> reponse ;
if (reponse==1) pfig = new cercle ;
else pfig = new point ;
pfig->affiche() ; // polymorphisme
return 0 ;
}
La classe figure est une classe abstraite car elle contient des fonctions
virtuelles non définies (symbole '=0'). Ce n'est pas une classe abstraite
pure puisqu'elle contient des membres définis (gestion de la couleur).
Cependant, il ne peut jamais y avoir de variable correspondant à
une classe abstraite, car un appel à une fonction non définie déclencherait
une erreur.