Le polymorphisme en C++ expliqué : Exemples pratiques et concepts clés
Qu'est-ce que le polymorphisme en C++ ?
En C++, le polymorphisme fait en sorte qu'une fonction membre se comporte différemment selon l'objet qui l'appelle/l'invoque. Le polymorphisme est un mot grec qui signifie « avoir plusieurs formes ». Il se produit quand vous avez une hiérarchie de classes liées par l'héritage.
Par exemple, supposons que nous ayons la fonction makeSound(). Quand un chat appelle cette fonction, elle produira le son miaou. Quand une vache invoque la même fonction, elle produira le son meuh.

Bien que nous ayons une seule fonction, elle se comporte différemment sous différentes circonstances. La fonction a plusieurs formes ; donc, nous avons atteint le polymorphisme.
Dans ce tutoriel C++, vous apprendrez :
- Qu'est-ce que le polymorphisme ?
- Types de polymorphisme
- Polymorphisme à la compilation
- Surcharge de fonctions
- Surcharge d'opérateurs
- Polymorphisme à l'exécution
- Redéfinition de fonctions (overriding)
- Fonctions virtuelles C++
- Polymorphisme à la compilation vs à l'exécution
Types de polymorphisme
C++ supporte deux types de polymorphisme :
- Polymorphisme à la compilation, et
- Polymorphisme à l'exécution.

Polymorphisme à la compilation
Vous invoquez les fonctions surchargées en faisant correspondre le nombre et le type d'arguments. L'information est présente pendant la compilation. Cela signifie que le compilateur C++ sélectionnera la bonne fonction au moment de la compilation.
Le polymorphisme à la compilation est réalisé par la surcharge de fonctions et la surcharge d'opérateurs.
Surcharge de fonctions
La surcharge de fonctions se produit quand nous avons plusieurs fonctions avec des noms similaires mais des arguments différents. Les arguments peuvent différer en nombre ou en type.
Exemple 1 :
#include <iostream>
using namespace std;
void test(int i) {
cout << " The int is " << i << endl;
}
void test(double f) {
cout << " The float is " << f << endl;
}
void test(char const *ch) {
cout << " The char* is " << ch << endl;
}
int main() {
test(5);
test(5.5);
test("five");
return 0;
}
Sortie :

Voici une capture d'écran du code :

Explication du code :
- Inclure le fichier d'en-tête iostream dans notre code. Nous pourrons utiliser ses fonctions.
- Inclure l'espace de noms std dans notre code. Nous pourrons utiliser ses classes sans l'appeler.
- Créer une fonction nommée test qui prend un paramètre entier i. Le { marque le début du corps de la fonction test.
- Instruction à exécuter si la fonction test ci-dessus est invoquée/appelée.
- Fin du corps de la fonction test ci-dessus.
- Créer une fonction nommée test qui prend un paramètre float f. Le { marque le début du corps de la fonction test.
- Instruction à exécuter si la fonction test ci-dessus est invoquée/appelée.
- Fin du corps de la fonction test ci-dessus.
- Créer une fonction nommée test qui prend un paramètre caractère ch. Le { marque le début du corps de la fonction test.
- Instruction à exécuter si la fonction test ci-dessus est invoquée/appelée.
- Fin du corps de la fonction test ci-dessus.
- Appeler la fonction main(). Le { marque le début du corps de la fonction.
- Appeler la fonction test et passer 5 comme valeur de l'argument. Cela invoque la fonction test qui accepte un argument entier, c'est-à-dire la première fonction test.
- Appeler la fonction test et passer 5.5 comme valeur de l'argument. Cela invoquera la fonction test qui accepte un argument float, c'est-à-dire la deuxième fonction test.
- Appeler la fonction test et passer "five" comme valeur de l'argument. Cela invoquera la fonction test qui accepte un argument caractère, c'est-à-dire la troisième fonction test.
- Le programme doit retourner une valeur s'il s'exécute avec succès.
- La fin du corps de la fonction main().
Nous avons trois fonctions avec le même nom mais différents types d'arguments. Nous avons atteint le polymorphisme.
Surcharge d'opérateurs
Dans la surcharge d'opérateurs, nous définissons une nouvelle signification pour un opérateur C++. Cela change aussi comment l'opérateur fonctionne. Par exemple, nous pouvons définir l'opérateur + pour concaténer deux chaînes. Nous le connaissons comme l'opérateur d'addition pour ajouter des valeurs numériques. Après notre définition, quand il est placé entre des entiers, il les additionnera. Quand il est placé entre des chaînes, il les concaténera.
Exemple 2 :
#include<iostream>
using namespace std;
class ComplexNum {
private:
int real, over;
public:
ComplexNum(int rl = 0, int ov = 0) {
real = rl;
over = ov;
}
ComplexNum operator + (ComplexNum const &obj) {
ComplexNum result;
result.real = real + obj.real;
result.over = over + obj.over;
return result;
}
void print() {
cout << real << " + i" << over << endl;
}
};
int main() {
ComplexNum c1(10, 2), c2(3, 7);
ComplexNum c3 = c1+c2;
c3.print();
}
Sortie :

Voici une capture d'écran du code :


Explication du code :
- Inclure le fichier d'en-tête iostream dans notre programme pour utiliser ses fonctions.
- Inclure l'espace de noms std dans notre programme pour utiliser ses classes sans l'appeler.
- Créer une classe nommée ComplexNum. Le { marque le début du corps de la classe.
- Utiliser le modificateur d'accès private pour marquer les variables comme privées, signifiant qu'elles ne peuvent être accédées que depuis la classe.
- Définir deux variables entières, real et over.
- Utiliser le modificateur d'accès public pour marquer le constructeur comme public, signifiant qu'il sera accessible même depuis l'extérieur de la classe.
- Créer le constructeur de classe et initialiser les variables.
- Initialiser la valeur de la variable real.
- Initialiser la valeur de la variable over.
- Fin du corps du constructeur.
- Nous devons redéfinir la signification de l'opérateur +.
- Créer le type de données result de type ComplexNum.
- Utiliser l'opérateur + avec des nombres complexes. Cette ligne additionnera la partie réelle d'un nombre à la partie réelle d'un autre nombre.
- Utiliser l'opérateur + avec des nombres complexes. Cette ligne additionnera la partie imaginaire d'un nombre à la partie imaginaire d'un autre nombre.
- Le programme retournera la valeur de la variable result en cas d'exécution réussie.
- Fin de la définition de la nouvelle signification de l'opérateur +, c'est-à-dire la surcharge.
- Appeler la méthode print().
- Afficher le nouveau nombre complexe après addition sur la console.
- Fin du corps de la fonction print().
- Fin du corps de la classe ComplexNum.
- Appeler la fonction main().
- Passer les valeurs des parties réelles et complexes à additionner. La première partie de c1 sera ajoutée à la première partie de c2, c'est-à-dire 10+3. La deuxième partie de c1 sera ajoutée à la deuxième partie de c2, c'est-à-dire 2+7.
- Effectuer une opération en utilisant l'opérateur + surchargé et stocker le résultat dans la variable c3.
- Afficher la valeur de la variable c3 sur la console.
- Fin du corps de la fonction main().
Polymorphisme à l'exécution
Cela se produit quand la méthode d'un objet est invoquée/appelée pendant l'exécution plutôt que pendant la compilation. Le polymorphisme à l'exécution est réalisé par la redéfinition de fonctions (overriding). La fonction à appeler/invoquer est établie pendant l'exécution.
Redéfinition de fonctions (Function Overriding)
La redéfinition de fonctions se produit quand une fonction de la classe de base reçoit une nouvelle définition dans une classe dérivée. À ce moment, on peut dire que la fonction de base a été redéfinie.
Par exemple :
#include <iostream>
using namespace std;
class Mammal {
public:
void eat() {
cout << "Mammals eat...";
}
};
class Cow: public Mammal {
public:
void eat() {
cout << "Cows eat grass...";
}
};
int main(void) {
Cow c = Cow();
c.eat();
return 0;
}
Sortie :

Voici une capture d'écran du code :

Explication du code :
- Importer le fichier d'en-tête iostream dans notre programme pour utiliser ses fonctions.
- Inclure l'espace de noms std dans notre programme pour utiliser ses classes sans l'appeler.
- Créer une classe nommée Mammal. Le { marque le début du corps de la classe.
- Utiliser le modificateur d'accès public pour rendre la fonction que nous allons créer publiquement accessible. Elle sera accessible depuis l'extérieur de cette classe.
- Créer une fonction publique nommée eat. Le { marque le début du corps de la fonction.
- Afficher l'instruction ajoutée à la fonction cout quand la fonction eat() est invoquée.
- La fin du corps de la fonction eat().
- Fin du corps de la classe Mammal.
- Créer une classe nommée Cow qui hérite de la classe Mammal. Cow est la classe dérivée, tandis que Mammal est la classe de base. Le { marque le début de cette classe.
- Utiliser le modificateur d'accès public pour marquer la fonction que nous allons créer comme publiquement accessible. Elle sera accessible depuis l'extérieur de cette classe.
- Redéfinir la fonction eat() qui était définie dans la classe de base. Le { marque le début du corps de la fonction.
- L'instruction à afficher sur la console quand cette fonction est invoquée.
- Fin du corps de la fonction eat().
- Fin du corps de la classe Cow.
- Appeler la fonction main(). Le { marque le début du corps de cette fonction.
- Créer une instance de la classe Cow et lui donner le nom c.
- Appeler la fonction eat() définie dans la classe Cow.
- Le programme doit retourner une valeur à l'issue d'une exécution réussie.
- Fin de la fonction main().
Fonction virtuelle C++
Une fonction virtuelle est une autre façon d'implémenter le polymorphisme à l'exécution en C++. C'est une fonction spéciale définie dans une classe de base et redéfinie dans la classe dérivée. Pour déclarer une fonction virtuelle, vous devez utiliser le mot-clé virtual. Le mot-clé doit précéder la déclaration de la fonction dans la classe de base.
Si une classe de fonction virtuelle est héritée, la classe virtuelle redéfinit la fonction virtuelle pour l'adapter à ses besoins. Par exemple :
#include <iostream>
using namespace std;
class ClassA {
public:
virtual void show() {
cout << "The show() function in base class invoked..." << endl;
}
};
class ClassB :public ClassA {
public:
void show() {
cout << "The show() function in derived class invoked...";
}
};
int main() {
ClassA* a;
ClassB b;
a = &b;
a->show();
}
Sortie :

Voici une capture d'écran du code :

Explication du code :
- Inclure le fichier d'en-tête iostream dans le code pour utiliser ses fonctions.
- Inclure l'espace de noms std dans notre code pour utiliser ses classes sans l'appeler.
- Créer une classe nommée ClassA.
- Utiliser le modificateur d'accès public pour marquer un membre de classe comme publiquement accessible.
- Créer une fonction virtuelle nommée show(). Ce sera une fonction publique.
- Le texte à afficher quand show() est invoqué. Le endl est un mot-clé C++, qui signifie fin de ligne. Il déplace le curseur de souris à la ligne suivante.
- Fin du corps de la fonction virtuelle show().
- Fin du corps de la classe ClassA.
- Créer une nouvelle classe nommée ClassB qui hérite de la classe ClassA. ClassA devient la classe de base tandis que ClassB devient la classe dérivée.
- Utiliser le modificateur d'accès public pour marquer un membre de classe comme publiquement accessible.
- Redéfinir la fonction virtuelle show() dérivée dans la classe de base.
- Le texte à afficher sur la console quand la fonction show() définie dans la classe dérivée est invoquée.
- Fin du corps de la fonction show().
- Fin du corps de la classe dérivée, ClassB.
- Appeler la fonction main(). La logique du programme doit être ajoutée dans son corps.
- Créer une variable pointeur nommée a. Elle pointe vers la classe nommée ClassA.
- Créer une instance de la classe nommée ClassB. L'instance est nommée b.
- Assigner les valeurs stockées dans l'adresse b dans la variable a.
- Invoquer la fonction show() définie dans la classe dérivée. Le liaison tardive a été implémentée.
- Fin du corps de la fonction main().
Polymorphisme à la compilation vs Polymorphisme à l'exécution
Voici les différences majeures entre les deux :
| Polymorphisme à la compilation | Polymorphisme à l'exécution |
|---|---|
| Également appelé liaison précoce ou polymorphisme statique | Également appelé liaison tardive/dynamique ou polymorphisme dynamique |
| La méthode est appelée/invoquée pendant la compilation | La méthode est appelée/invoquée pendant l'exécution |
| Implémenté via surcharge de fonctions et surcharge d'opérateurs | Implémenté via redéfinition de méthodes et fonctions virtuelles |
| Exemple, surcharge de méthodes. Plusieurs méthodes peuvent avoir des noms similaires mais différents nombres ou types d'arguments | Exemple, redéfinition de méthodes. Plusieurs méthodes peuvent avoir un nom similaire et le même prototype |
| Exécution plus rapide car la découverte des méthodes se fait pendant la compilation | Exécution plus lente car la découverte des méthodes se fait pendant l'exécution |
| Moins de flexibilité pour la résolution de problèmes car tout est connu pendant la compilation | Beaucoup de flexibilité fournie pour résoudre des problèmes complexes car les méthodes sont découvertes pendant l'exécution |
Résumé :
- Le polymorphisme signifie avoir plusieurs formes.
- Il se produit quand il y a une hiérarchie de classes liées par l'héritage.
- Avec le polymorphisme, une fonction peut se comporter différemment selon l'objet qui l'invoque/l'appelle.
- Dans le polymorphisme à la compilation, la fonction à invoquer est établie pendant la compilation.
- Dans le polymorphisme à l'exécution, la fonction à invoquer est établie pendant l'exécution.
- Le polymorphisme à la compilation est déterminé par la surcharge de fonctions et la surcharge d'opérateurs.
- Dans la surcharge de fonctions, il y a plusieurs fonctions avec des noms similaires mais des arguments différents.
- Les paramètres peuvent différer en nombre ou en type.
- Dans la surcharge d'opérateurs, une nouvelle signification est définie pour les opérateurs C++.
- Le polymorphisme à l'exécution est atteint par la redéfinition de fonctions.
- Dans la redéfinition de fonctions, une classe dérivée donne une nouvelle définition à une fonction définie dans la classe de base.
Langue C
- Comment passer des tableaux aux fonctions en C++ : Guide pratique
- Opérateurs C++ expliqués : Types, exemples et programmes d'exemple
- Maîtriser le switch-case en C++ : Syntaxe, utilisation et exemples pratiques
- Maîtriser l'allocation de tableaux dynamiques en C++ : Guide pratique avec exemples de code
- Surcharge d'opérateurs en C++ : Guide pratique avec exemples de code
- Les structures en C++ expliquées avec un exemple pratique
- Maîtriser std::list en C++ : Syntaxe, Fonctions et Exemples Pratiques
- Les Fonctions en C++ Expliquées avec des Exemples de Code Pratiques
- Struct vs Class en C++ : Différences Clés, Syntaxe et Cas d'Utilisation Optimaux