Les fonctions virtuelles permettent de réaliser le polymorphisme — la possibilité de traiter des objets de classes dérivées via des pointeurs ou des références à une classe de base. Pour déclarer une fonction virtuelle, on utilise le mot-clé virtual :
class Base { public: virtual void foo() { std::cout << "Base::foo"; } }; class Derived : public Base { public: void foo() override { std::cout << "Derived::foo"; } };
L'appel de foo() via un pointeur de type Base* appelle l'implémentation dans Derived, si celui-ci pointe vers un objet Derived.
Classe abstraite — contient au moins une fonction virtuelle pure :
class Interface { public: virtual void process() = 0; // fonction virtuelle pure };
Héritage virtuel résout le problème de l'héritage en diamant (diamond problem) :
class A { }; class B : virtual public A { }; class C : virtual public A { }; class D : public B, public C { };
Cela garantit qu'il n'y aura qu'une seule instance de la classe de base A dans l'objet D.
Quelle est la différence entre un destructeur virtuel et un destructeur normal ? Faut-il rendre le destructeur virtuel si la classe est destinée à être héritée ?
Réponse : Un destructeur virtuel garantit que lors de la suppression via un pointeur de type de base, le destructeur de la classe dérivée sera appelé. Cela est important pour la libération correcte des ressources.
class Base { public: virtual ~Base() {} };
Si le destructeur n'est pas virtuel, alors lors de delete BasePtr;, seul le destructeur Base sera appelé pour l'objet de la classe dérivée, les ressources des champs hérités ne seront pas libérées.
Histoire
Dans un grand système financier, où il y avait une interface commune de classe pour divers instruments, le destructeur virtuel n'avait pas été déclaré. Dans l'un des héritiers, une ressource dynamique était utilisée. Lors de la suppression de l'objet via un pointeur de type de base, une fuite de mémoire s'est produite, détectée uniquement lors des charges industrielles.
Histoire
L'équipe a utilisé l'héritage multiple sans appliquer l'héritage virtuel. La classe D a hérité deux fois de A via des classes intermédiaires. Cela a conduit à une duplication de l'état et à des erreurs lors de l'accès aux membres de A. Cela n'a été corrigé qu'après un audit par des outils d'analyse statique.
Histoire
Dans un projet de développement de plugins de journalisation, une classe abstraite a été utilisée, mais son destructeur n'a pas été rendu virtuel. Lors de la suppression des plugins via un pointeur d'interface, des dépendances non évidentes et des bogues liés aux destructeurs non appelés des descendants ont été observés. Ce problème affectait le pool de ressources et entraînait des fuites de mémoire.