C++ Polymorphisme
PolymorphismeLittéralement, cela signifie plusieurs formes.
Lorsqu'il existe une structure hiérarchique entre les classes, et que les classes sont liées par héritage, le polymorphisme est utilisé.
En C++, le polymorphisme est l'une des caractéristiques importantes de la programmation orientée objet.
Le polymorphisme en C++ permet d'utiliser des pointeurs ou des références de classe de base pour appeler des méthodes redéfinies de classes dérivées, afin qu'une même interface puisse présenter des comportements différents.
Le polymorphisme rend le code plus flexible et plus général. Le programme peut manipuler des objets de types différents via des pointeurs ou des références de classe de base sans distinguer explicitement les types d'objets. Cela rend le code plus extensible ; par exemple, lors de l'ajout d'une nouvelle classe de forme, le programme principal n'a pas besoin d'être modifié.
Voici quelques points clés concernant le polymorphisme :
Fonctions virtuelles:
- Déclarer une fonction dans la classe de base comme fonction virtuelle utilise le mot-clé
virtual。 - Une classe dérivée peut redéfinir cette fonction virtuelle.
- Lorsqu'une fonction virtuelle est appelée, la version de la fonction appelée est déterminée par le type réel de l'objet.
Liaison dynamique:
- Aussi appelée liaison tardive, l'implémentation spécifique d'un appel de fonction est déterminée à l'exécution.
- Cela nécessite d'utiliser un pointeur ou une référence vers la classe de base pour appeler des fonctions virtuelles ; à l'exécution, le compilateur décide quelle fonction appeler en fonction du type réel de l'objet.
Fonctions virtuelles pures:
- Une classe contenant des fonctions virtuelles pures est appelée classe abstraite, et elle ne peut pas être instanciée directement.
- Une fonction virtuelle pure n'a pas de corps de fonction ; lorsqu'elle est déclarée, elle utilise le mot-clé
= 0。 - Elle force les classes dérivées à fournir des implémentations concrètes.
Le mécanisme d'implémentation du polymorphisme:
- Table des fonctions virtuelles (V-Table) : Le runtime C++ utilise une table des fonctions virtuelles pour implémenter le polymorphisme. Chaque classe contenant des fonctions virtuelles possède une table des fonctions virtuelles, qui stocke des pointeurs vers toutes les fonctions virtuelles de la classe.
- Pointeur de fonction virtuelle (V-Ptr) : L'objet contient un pointeur vers la table des fonctions virtuelles de la classe.
Les avantages de l'utilisation du polymorphisme:
- Réutilisation du codeGrâce aux pointeurs ou aux références de classe de base, différents types d'objets de classes dérivées peuvent être manipulés, permettant ainsi de réutiliser le code.
- ExtensibilitéLors de l'ajout d'une nouvelle classe dérivée, le code qui dépend de la classe de base n'a pas besoin d'être modifié ; il suffit de s'assurer que la nouvelle classe redéfinit correctement les fonctions virtuelles.
- DécouplageLe polymorphisme permet une conception de programme plus modulaire et réduit le couplage entre les classes.
Notes:
- Le polymorphisme ne se produit que lorsqu'une fonction virtuelle est appelée via un pointeur ou une référence de classe de base.
- Si un objet de classe dérivée est utilisé directement pour appeler une fonction, c'est la version de la classe dérivée qui est appelée, et non celle de la classe de base.
- Le polymorphisme nécessite des informations de type à l'exécution (RTTI), ce qui peut augmenter le coût de fonctionnement du programme.
Exemple 1
Comprenons l'application du polymorphisme à travers un exemple simple:
Exemple 1
using namespace std;
// Classe de base Animal
class Animal {
public:
// Fonction virtuelle sound, fournissant une interface pour que différents animaux émettent des sons
virtual void sound() const {
cout << "Animal makes a sound" << endl;
}
// Le destructeur virtuel garantit que les objets de la classe dérivée sont correctement détruits
virtual ~Animal() {
cout << "Animal destroyed" << endl;
}
};
// Classe dérivée Dog, héritant de Animal
class Dog : public Animal {
public:
// Redéfinir la méthode sound
void sound() const override {
cout << "Dog barks" << endl;
}
~Dog() {
cout << "Dog détruit" << endl;
}
};
// Classe dérivée Cat, héritant d'Animal
class Cat : public Animal {
public:
// Redéfinir la méthode sound
void sound() const override {
cout << "Cat miaule" << endl;
}
~Cat() {
cout << "Cat détruit" << endl;
}
};
// Test du polymorphisme
int main() {
Animal* animalPtr; // Pointeur de classe de base
// Créer un objet Dog et pointer vers lui avec un pointeur Animal
animalPtr = new Dog();
animalPtr->sound(); // Appeler la méthode sound de Dog
delete animalPtr; // Libérer la mémoire, appeler les destructeurs Dog et Animal
// Créer un objet Cat et pointer vers lui avec un pointeur Animal
animalPtr = new Cat();
animalPtr->sound(); // Appeler la méthode sound de Cat
delete animalPtr; // Libérer la mémoire, appeler les destructeurs Cat et Animal
return 0;
}
La sortie d'exécution du programme est :
Dog barks Dog destroyed Animal destroyed Cat meows Cat destroyed Animal destroyed
Explication de code
classe de baseAnimal:
AnimalLa classe définit une fonction virtuellesound(), qui est une fonction virtuelle (virtual), utilisée pour représenter le comportement des animaux émettant des sons.~Animal()Le fait d'être un destructeur virtuel garantit que lorsqu'un objet de classe dérivée pointé par un pointeur de classe de base est libéré, le destructeur de la classe dérivée est correctement appelé, ce qui évite les fuites de mémoire.
classe dérivéeDogetCat:
DogetCatToutes les classes héritent deAnimalles classes en dérivent, et chacune implémentesound()méthode.Dogdesound()Affiche "Dog barks";Catdesound()Affiche "Cat meows". Cela fait que la même méthode (sound()) présente des comportements différents dans différentes classes.
fonction mainmain():
- Créez un pointeur de classe de base
animalPtr。 - Utilisation
new Dog()CréezDogobjet, affectez son adresse àanimalPtr. À ce stade, l'appelanimalPtr->sound()Cela affichera "Dog barks", caranimalPtrpointe en réalité versDogObjet. - libérer
Doglorsque l'objet, premier appelDogdestructeur, puis appelerAnimalle destructeur de. - Utilisation
new Cat()CréezCatobjet et affecter àanimalPtr, puis appeleranimalPtr->sound(), affiche "Cat meows", montrant un comportement polymorphe.
Concepts clés
fonction virtuelle: En utilisant dans la classe de base
virtualmot-clé pour déclarer des fonctions virtuelles, les classes dérivées peuvent redéfinir cette fonction, afin qu'à l'exécution la bonne fonction soit appelée en fonction du type de l'objet.liaison dynamique: Le polymorphisme en C++ est mis en œuvre par la liaison dynamique. À l'exécution, le pointeur de classe de base
animalPtrva, en fonction du type réel de l'objet vers lequel il pointe (DogouCat) appeler la correspondantesound()méthode.destructeur virtuelDans une classe de base avec un comportement polymorphe, le destructeur doit être déclaré comme
virtual, afin de garantir que lorsqu’un objet de classe dérivée est supprimé, le destructeur de la classe dérivée est appelé, évitant ainsi les fuites de ressources.
Exemple 2
Dans l’exemple ci-dessous, nous implémentons une classe de base commune Shape et deux classes dérivées, Rectangle et Triangle, via le polymorphisme.
L’appel de méthodes de différentes classes dérivées via un pointeur de classe de base démontre la liaison dynamique du polymorphisme.
Exemple 2
Lorsque le code ci-dessus est compilé et exécuté, il produit les résultats suivants :
Rectangle Area: Rectangle class area: 70 Triangle Area: Triangle class area: 25
Analyse de code
Définition de la classe Shape :
Shapeest une classe de base abstraite, qui définit une fonction virtuellearea()。area()est une fonction virtuelle utilisée pour calculer l'aire, et elle utilise levirtualmot-clé, afin que dans les classes dérivées la fonction puisse être redéfinie, ce qui permet d'obtenir le polymorphisme.widthetheightOuiprotectedpropriété, ne peut êtreShapeaccessible que dans la classe et ses classes dérivées.
// 基类 Shape,表示形状
class Shape {
protected:
int width, height; // 宽度和高度
public:
// 构造函数,带有默认参数
Shape(int a = 0, int b = 0) : width(a), height(b) { }
// 虚函数 area,用于计算面积
virtual int area() {
cout << "Shape class area: " << endl;
return 0;
}
};
Définition de la classe Rectangle :
RectanglehériteShapeclasse, et a redéfiniarea()méthode, calcule l'aire du rectangle.area()La méthode utiliseoverridemot-clé, indiquant qu'il s'agit d'une redéfinition de la classe de baseShapedearea()Redéfinition de méthode.Rectangle::area()returnwidth * height, c'est-à-dire l'aire du rectangle.
// 派生类 Rectangle,表示矩形
class Rectangle : public Shape {
public:
// 构造函数,使用基类构造函数初始化 width 和 height
Rectangle(int a = 0, int b = 0) : Shape(a, b) { }
// 重写 area 函数,计算矩形面积
int area() override {
cout << "Rectangle class area: " << endl;
return width * height;
}
};Définition de la classe Triangle :
Trianglela classe hérite également deShape, et redéfinitarea()méthode, utilisée pour calculer l'aire du triangle.Triangle::area()returnwidth * height / 2, c'est la formule de l'aire d'un triangle.
// 派生类 Triangle,表示三角形
class Triangle : public Shape {
public:
// 构造函数,使用基类构造函数初始化 width 和 height
Triangle(int a = 0, int b = 0) : Shape(a, b) { }
// 重写 area 函数,计算三角形面积
int area() override {
cout << "Triangle class area: " << endl;
return (width * height / 2);
}
};
Comportement polymorphe dans la fonction main :
- Un pointeur de classe de base est défini
shape, ce pointeur peut pointer vers n'importe quelShapeobjet de classe ou un objet de ses classes dérivées. - Tout d'abord, faites pointer
shapele pointeur versRectangleObjectrec, puis appelershape->area(). Commearea()est une fonction virtuelle, et à ce stade elle se liera dynamiquement àRectangle::area(), affichant l'aire du rectangle. - Ensuite, faites pointer
shapele pointeur versTriangleObjecttri, appelezshape->area()sera lié dynamiquement àTriangle::area(), en affichant l'aire du triangle.
// 主函数
int main() {
Shape *shape; // 基类指针
Rectangle rec(10, 7); // 矩形对象
Triangle tri(10, 5); // 三角形对象
// 将基类指针指向矩形对象,并调用 area 函数
shape = &rec;
cout << "Rectangle Area: " << shape->area() << endl;
// 将基类指针指向三角形对象,并调用 area 函数
shape = &tri;
cout << "Triangle Area: " << shape->area() << endl;
return 0;
}
Concepts clés
fonction virtuelle: dans la classe de base
Shapeles fonctions virtuelles sont définies dansarea(). Le rôle d'une fonction virtuelle est de permettre aux classes dérivées de redéfinir cette fonction et, à l'exécution, d'appeler l'implémentation de fonction appropriée en fonction du type réel de l'objet vers lequel pointe le pointeur.liaison dynamique: car
area()est une fonction virtuelle,shape->area()lorsqu'elle est appelée, à l'exécution, en fonction deshapetype réel de l'objet vers lequel il pointe (RectangleouTriangle) pour appeler laarea()implémentation correspondante. Ce mécanisme qui consiste à décider quelle fonction appeler à l'exécution est appelé liaison dynamique et constitue le cœur du polymorphisme.polymorphisme des pointeurs de classe de base: le pointeur de classe de base
shapepeut pointer vers tout objet dérivé deShapede l'objet. Lorsqueshapelorsqu'il pointe vers différents objets de classes dérivées, l'appelshape->area()produira des comportements différents, ce qui reflète les caractéristiques du polymorphisme.- peut avoir une implémentation dans la classe de base. Généralement, les fonctions virtuelles fournissent une implémentation par défaut dans la classe de base, mais les classes dérivées peuvent choisir de les redéfinir.
- liaison dynamique: à l'exécution, appeler la version correspondante de la fonction en fonction du type réel de l'objet.
- Redéfinition facultativeLes classes dérivées peuvent éventuellement redéfinir les fonctions virtuelles, mais ce n'est pas obligatoire.
- doit être déclarée dans la classe de base comme
= 0, indiquant qu'il n'y a pas d'implémentation, et les classes dérivées doivent la redéfinir. - Classe abstraite: une classe contenant des fonctions virtuelles pures ne peut pas être instanciée directement ; un objet ne peut être créé que lorsque toutes les fonctions virtuelles pures sont implémentées par une classe dérivée.
- Définition d'interfaceLes fonctions virtuelles pures sont généralement utilisées pour définir des interfaces, permettant aux classes dérivées d'implémenter des comportements spécifiques.
fonction virtuelle
Une fonction virtuelleest une fonction déclarée à l'aide du mot-clévirtualdans la classe de base.
La fonction virtuellepermet aux sous-classes de la redéfinir, de sorte qu'à l'exécution la version redéfinie de la sous-classe est appelée via un pointeur ou une référence de classe de base, mettant en œuvre la liaison dynamique.
Ce que nous voulons, c'est qu'à tout moment dans le programme, la fonction à appeler puisse être sélectionnée en fonction du type de l'objet appelé. Cette opération est appeléeliaison dynamique, ouliaison tardive.
Fonctionnalités :
Exemple
using namespace std;
class Animal {
public:
virtual void sound() { // Fonction virtuelle
cout << "Animal makes a sound" << endl;
}
};
class Dog : public Animal {
public:
void sound() override { // Redéfinir la fonction virtuelle
cout << "Dog barks" << endl;
}
};
int main() {
Animal *animal = new Dog();
animal->sound(); // Sortie : Dog barks
delete animal;
}
Dans le code ci-dessus, sound est une fonction virtuelle de la classe Animal. Lors de l'appel de sound() via le pointeur Animal* animal, le programme sélectionne Dog::sound() en fonction du type réel de l'objet (Dog).
Fonction virtuelle pure
Vous pouvez vouloir définir une fonction virtuelle dans la classe de base afin de pouvoir redéfinir la fonction dans une classe dérivée pour mieux convenir à l'objet, mais vous ne pouvez pas fournir d'implémentation pertinente pour la fonction virtuelle dans la classe de base. Dans ce cas, une fonction virtuelle pure est utilisée.
Une fonction virtuelle pure est une fonction virtuelle sans implémentation ; dans la classe de base, elle est déclarée avec= 0pour la déclarer.
Une fonction virtuelle pure signifie que la classe de base définit une interface, mais l'implémentation spécifique incombe à la classe dérivée.
Les fonctions virtuelles pures font de la classe de base une classe abstraite, qui ne peut pas être instanciée.
Fonctionnalités :
Nous pouvons réécrire la fonction virtuelle area() de la classe de base comme suit :
= 0indique au compilateur que la fonction n'a pas de corps ; la fonction virtuelle ci-dessus estFonction virtuelle pure。
Comparaison des fonctions virtuelles et des fonctions virtuelles pures
| Fonctionnalités | Fonction virtuelle | Fonction virtuelle pure |
|---|---|---|
| définition | Utilisée dans la classe de basevirtualDéclaration, avec implémentation | Utilisée dans la classe de base= 0Déclaration, sans implémentation |
| Redéfinie dans la classe dérivée | Les sous-classes peuvent choisir de la redéfinir | La sous-classe doit l'implémenter |
| Caractère abstrait | peut instancier la classe | fait de la classe une classe abstraite, qui ne peut pas être instanciée |
| Objectif | fournit un comportement par défaut, permettant aux sous-classes de le redéfinir | définit une interface, obligeant les classes dérivées à implémenter des comportements spécifiques |