Fonctions virtuelles en C++ : pourquoi sont-elles indispensables ?
\n\nImaginez que vous êtes un réalisateur qui tourne un film sur les animaux. Vous avez un scénario : "Tous les animaux émettent un son". Vous tournez une scène avec un chien — il aboie. Une scène avec un chat — il miaule. Tout est logique. Maintenant, imaginez que votre assistant (le compilateur) vous apporte une boîte d'animaux en jouet et vous dit : "Tournez !". Vous appuyez sur le bouton, et le jouet... reste silencieux. Pourquoi ? Parce que vous avez oublié les fonctions virtuelles.
\n\nEn programmation C++, ce problème se rencontre constamment. Vous écrivez une classe "Animal", avec une méthode sound() à l'intérieur. Ensuite, vous créez les classes "Chien" et "Chat", qui héritent de "Animal". Et tout semble aller bien, jusqu'à ce que vous essayiez de travailler avec ces objets via un pointeur sur la classe de base. C'est là que vous découvrez que C++ appelle par défaut la méthode de la classe de base, et non celle redéfinie dans la classe dérivée. Les fonctions virtuelles résolvent ce problème une fois pour toutes.
Dans cet article, nous analyserons en détail ce que sont les fonctions virtuelles, pourquoi elles sont nécessaires, comment elles fonctionnent sous le capot, et pourquoi le C++ moderne est inconcevable sans elles.
\n\nQu'est-ce qu'une fonction virtuelle en termes simples
\n\nUne fonction virtuelle est une méthode de classe que vous marquez avec le mot-clé virtual. Une telle fonction peut être redéfinie dans les classes dérivées, et lors de l'appel via un pointeur ou une référence sur la classe de base, c'est la version de la classe dérivée qui sera appelée.
Cela semble compliqué ? Prenons un exemple de la vie réelle. Imaginez une télécommande pour des robots. Vous avez une classe de base "Robot" avec une méthode doWork(). Il y a un robot aspirateur et un robot livreur. Si vous appuyez sur le bouton "Travailler", les algorithmes correspondants doivent se lancer : l'aspirateur commence à nettoyer, le livreur à livrer un colis. Sans fonctions virtuelles, vous appuieriez sur le bouton, et le robot resterait simplement immobile (la méthode de la classe de base serait appelée).
En C++, les fonctions virtuelles sont la base du polymorphisme. Le polymorphisme permet de travailler de manière uniforme avec des objets de différents types, s'ils héritent d'un ancêtre commun.
\n\nLe problème sans fonctions virtuelles : un exemple concret
\n\nVoyons ce qui se passe si vous n'utilisez pas virtual.
#include <iostream>\n#include <vector>\n\nclass Animal {\npublic:\n void sound() {\n std::cout << "Some generic animal sound" << std::endl;\n }\n};\n\nclass Dog : public Animal {\npublic:\n void sound() {\n std::cout << "Woof! Woof!" << std::endl;\n }\n};\n\nclass Cat : public Animal {\npublic:\n void sound() {\n std::cout << "Meow!" << std::endl;\n }\n};\n\nint main() {\n std::vector<Animal*> animals;\n animals.push_back(new Dog());\n animals.push_back(new Cat());\n\n for (Animal* a : animals) {\n a->sound(); // Que va-t-il s'afficher ?\n }\n\n // Nettoyage de la mémoire\n for (Animal* a : animals) delete a;\n\n return 0;\n}\n\n\nExécutez ce code. Vous verrez :
\nSome generic animal sound\nSome generic animal sound\n\n\nPourquoi ? Parce que le compilateur regarde le type du pointeur (Animal*), et non l'objet réel. Il appelle la méthode de la classe de base. C'est ce qu'on appelle la liaison précoce (early binding) — la décision sur la méthode à appeler est prise au moment de la compilation.
Or, nous voulons une liaison tardive (late binding) — pour que la décision soit prise pendant l'exécution du programme, en fonction du type de l'objet. C'est exactement ce que permettent les fonctions virtuelles.
\n\nLa solution : ajouter virtual
\n\nCorrigeons l'exemple en ajoutant le mot-clé virtual.
class Animal {\npublic:\n virtual void sound() { // Nous avons ajouté virtual\n std::cout << "Some generic animal sound" <<