Next: La généricité Up: Le langage C++ Previous: Ordre de recherche

Pointeurs et polymorphisme

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.

@
vendredi, 7 novembre 1997, 14:51:48 MET