Le Polymorphisme dans C++

de | 26 octobre 2018


Avant de s’approfondir dans ce chapitre, vous devez avoir une bonne compréhension des pointeurs et de l’héritage de classe. Si vous n’êtes pas vraiment sûr de la signification de l’une des expressions suivantes, vous devez consulter les sections indiquées:

Instructions : Chapitre :
int A::b(int c) { } Les classes
a->b La structures des données
class A: public B {}; Ami et héritage

Pointeurs vers la classe de base

L’une des principales caractéristiques de l’héritage de classe est qu’un pointeur sur une classe dérivée est compatible avec le type avec un pointeur sur sa classe de base. Le polymorphisme est l’art de tirer parti de cette fonctionnalité simple mais puissante et polyvalente.

L’exemple des classes rectangle et triangle peut être réécrit à l’aide de pointeurs prenant en compte cette fonctionnalité:

// pointers to base class
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
};

class Rectangle: public Polygon {
public:
int area()
{ return width*height; }
};

class Triangle: public Polygon {
public:
int area()
{ return width*height/2; }
};

int main () {
Rectangle rect;
Triangle trgl;
Polygon * ppoly1 = &rect;
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << rect.area() << '\n';
cout << trgl.area() << '\n';
return 0;
}
Résultat de l’exécution : 10, 20

La fonction main déclare deux pointeurs sur Polygon (nommés ppoly1 et ppoly2). Celles-ci se voient attribuer les adresses respectives de rect et trgl, qui sont des objets de type Rectangle et Triangle. Ces affectations sont valides, car Rectangle et Triangle sont des classes dérivées de Polygon.

Le déréférencement de ppoly1 et ppoly2 (avec * ppoly1 et * ppoly2) est valide et nous permet d’accéder aux membres de leurs objets pointus. Par exemple, les deux instructions suivantes seraient équivalentes dans l’exemple précédent:

ppoly1-> set_values ​​(4,5);
rect.set_values ​​(4,5);

Mais comme le type de ppoly1 et ppoly2 est un pointeur sur Polygon (et non sur Rectangle ni sur Triangle), seuls les membres hérités de Polygon sont accessibles, et non ceux des classes dérivées Rectangle et Triangle. C’est pourquoi le programme ci-dessus accède aux membres de la zone des deux objets en utilisant rect et trgl directement, au lieu des pointeurs; les pointeurs vers la classe de base ne peuvent pas accéder aux membres de la zone.

On aurait pu accéder à zone membre avec les pointeurs sur Polygone si zone était un membre de Polygon et non un membre de ses classes dérivées, mais le problème est que Rectangle et Triangle implémentent des versions différentes de la zone. pourrait être implémenté dans la classe de base.

Membres virtuels

Un membre virtuel est une fonction membre qui peut être redéfinie dans une classe dérivée, tout en préservant ses propriétés d’appel par le biais de références. La syntaxe pour qu’une fonction devienne virtuelle consiste à faire précéder sa déclaration avec le mot clé virtual:

// virtual members
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area ()
{ return 0; }
};

class Rectangle: public Polygon {
public:
int area ()
{ return width * height; }
};

class Triangle: public Polygon {
public:
int area ()
{ return (width * height / 2); }
};

int main () {
Rectangle rect;
Triangle trgl;
Polygon poly;
Polygon * ppoly1 = &rect;
Polygon * ppoly2 = &trgl;
Polygon * ppoly3 = &poly;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
ppoly3->set_values (4,5);
cout << ppoly1->area() << '\n';
cout << ppoly2->area() << '\n';
cout << ppoly3->area() << '\n';
return 0;
}

Résultat de l’exécution : 20, 10, 0

Dans cet exemple, les trois classes (Polygon, Rectangle et Triangle) ont les mêmes membres: width, height et les fonctions set_values ​​et area.

À lire aussi  C++ Exercices avec solutions

La zone de fonction membre a été déclarée virtuelle dans la classe de base car elle est redéfinie ultérieurement dans chacune des classes dérivées. Les membres non virtuels peuvent également être redéfinis dans des classes dérivées, mais les membres non virtuels des classes dérivées ne sont pas accessibles via une référence de la classe de base: par exemple, si virtual est supprimé de la déclaration de zone de l’exemple ci-dessus, les trois appels to area renverrait zéro, car dans tous les cas, la version de la classe de base aurait été appelée à la place.

Par conséquent, le mot-clé virtual permet essentiellement à un membre d’une classe dérivée portant le même nom que celui de la classe de base d’être appelé de manière appropriée à partir d’un pointeur, et plus précisément lorsque le type du pointeur est un pointeur vers le pointeur. classe de base qui pointe vers un objet de la classe dérivée, comme dans l’exemple ci-dessus.

Une classe qui déclare ou hérite d’une fonction virtuelle est appelée classe polymorphe.

Notez qu’en dépit de la virtualité de l’un de ses membres, Polygon était une classe régulière, dont même un objet était instancié (poly), avec sa propre définition de zone de membre qui renvoie toujours 0.

Classes abstraites de base

Les classes de base abstraites ressemblent beaucoup à la classe Polygon de l’exemple précédent. Ce sont des classes qui ne peuvent être utilisées que comme classes de base, et sont donc autorisées à avoir des fonctions membres virtuelles sans définition (connues sous le nom de fonctions virtuelles pures). La syntaxe consiste à remplacer leur définition par = 0 (un signe égal et un zéro):

Une classe de polygones de base abstraite pourrait ressembler à ceci:

// abstract class CPolygon
class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area () =0;
};

Notez que cette zone n’a pas de définition; ceci a été remplacé par = 0, ce qui en fait une fonction virtuelle pure. Les classes contenant au moins une fonction virtuelle pure sont appelées classes de base abstraites.

Les classes de base abstraites ne peuvent pas être utilisées pour instancier des objets. Par conséquent, cette dernière version de classe de base abstraite de Polygon ne peut pas être utilisée pour déclarer des objets tels que:

Polygone mypolygon; // ne fonctionne pas si Polygon est une classe de base abstraite

Mais une classe de base abstraite n’est pas totalement inutile. Il peut être utilisé pour créer des pointeurs et tirer parti de toutes ses capacités polymorphes. Par exemple, les déclarations de pointeur suivantes seraient valides:

Polygone * ppoly1;
Polygone * ppoly2;

Et peut réellement être déréférencé lorsque vous pointez sur des objets de classes dérivées (non abstraites). Voici l’exemple complet:

// abstract base class
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area (void) =0;
};

class Rectangle: public Polygon {
public:
int area (void)
{ return (width * height); }
};

class Triangle: public Polygon {
public:
int area (void)
{ return (width * height / 2); }
};

int main () {
Rectangle rect;
Triangle trgl;
Polygon * ppoly1 = &rect;
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
cout << ppoly1->area() << '\n';
cout << ppoly2->area() << '\n';
return 0;
}

Résultat de l’exécution : 20, 10

Dans cet exemple, les objets de types différents mais liés sont référencés à l’aide d’un type de pointeur unique (Polygon *) et la fonction de membre appropriée est appelée à chaque fois, simplement parce qu’ils sont virtuels. Cela peut être vraiment utile dans certaines circonstances. Par exemple, il est même possible pour un membre de la classe de base abstraite Polygon d’utiliser le pointeur spécial this pour accéder aux membres virtuels appropriés, même si Polygon lui-même n’a pas d’implémentation pour cette fonction:

// un membre virtuel peut être appeler a partir d'une classe abstraite
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
void set_values (int a, int b)
{ width=a; height=b; }
virtual int area() =0;
void printarea()
{ cout << this->area() << '\n'; }
};

class Rectangle: public Polygon {
public:
int area (void)
{ return (width * height); }
};

class Triangle: public Polygon {
public:
int area (void)
{ return (width * height / 2); }
};

int main () {
Rectangle rect;
Triangle trgl;
Polygon * ppoly1 = &rect;
Polygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
ppoly1->printarea();
ppoly2->printarea();
return 0;
}

Les membres virtuels et les classes abstraites accordent des caractéristiques polymorphes C ++, très utiles pour les projets orientés objet. Bien entendu, les exemples ci-dessus sont des cas d’utilisation très simples, mais ces fonctionnalités peuvent être appliquées à des tableaux d’objets ou à des objets alloués dynamiquement.

À lire aussi  Les opérateurs dans C++

Voici un exemple qui combine certaines des fonctionnalités des derniers chapitres, telles que la mémoire dynamique, les initialiseurs de constructeurs et le polymorphisme:

// allocation dynamique et polymorphism
#include <iostream>
using namespace std;

class Polygon {
protected:
int width, height;
public:
Polygon (int a, int b) : width(a), height(b) {}
virtual int area (void) =0;
void printarea()
{ cout << this->area() << '\n'; }
};

class Rectangle: public Polygon {
public:
Rectangle(int a,int b) : Polygon(a,b) {}
int area()
{ return width*height; }
};

class Triangle: public Polygon {
public:
Triangle(int a,int b) : Polygon(a,b) {}
int area()
{ return width*height/2; }
};

int main () {
Polygon * ppoly1 = new Rectangle (4,5);
Polygon * ppoly2 = new Triangle (4,5);
ppoly1->printarea();
ppoly2->printarea();
delete ppoly1;
delete ppoly2;
return 0;
}

Notez que les pointeurs de ppoly:

Polygone * ppoly1 = new Rectangle (4,5);
Polygone * ppoly2 = new Triangle (4,5);

sont déclarés comme étant de type « pointeur sur polygone », mais les objets affectés ont été déclarés comme ayant directement le type de classe dérivé (Rectangle et Triangle).




Catégorie : C++ Étiquettes :

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *