En C++, le polymorphisme dynamique est mis en œuvre par le biais de mécanisme des fonctions virtuelles à l'aide d'une table spéciale — vtable (table virtuelle). Pour toute classe ayant au moins une fonction virtuelle, le compilateur génère une vtable : c'est un tableau de pointeurs vers les fonctions virtuelles de la classe. Chaque objet de cette classe contient un pointeur caché vers la vtable — appelé vptr (pointeur virtuel).
Au moment de la création d'un objet d'une classe avec des fonctions virtuelles, le vptr est initialisé à la table vtable correspondant à la classe de l'objet. Lorsqu'une fonction virtuelle est appelée, l'appel ne se fait pas directement, mais via l'adresse stockée dans la vtable, choisissant la bonne implémentation en fonction du type réel de l'objet (même lorsqu'il est référencé par le type de base).
class Base { public: virtual void foo() { std::cout << "Base::foo() "; } }; class Derived : public Base { public: void foo() override { std::cout << "Derived::foo() "; } }; void call_foo(Base* obj) { obj->foo(); // appel via vtable }
L'appel à foo sera un accès à la vtable : si c'est dans Derived, la fonction redéfinie sera appelée.
"Un méthode est-elle virtuelle si elle est déclarée comme
virtual, mais n'est pas redéfinie dans la classe dérivée ? L'appel via vtable fonctionnera-t-il dans ce cas ?"
Réponse : Oui, si la fonction est déclarée comme virtual dans la classe de base, elle reste virtuelle pour tous les descendants, même si elle n'est pas redéfinie. L'appel via un pointeur de base se fera toutefois par la vtable. Si la méthode n'est pas redéfinie, la version de la classe de base sera appelée.
Exemple :
struct Base { virtual void foo() { std::cout << "B "; } }; struct Derived : Base { }; Base* obj = new Derived(); obj->foo(); // Affichera "B", mais appel via vtable !
Histoire
Dans un grand projet, on a oublié de déclarer le destructeur comme virtuel dans la classe de base avec des fonctions virtuelles. Cela a conduit à des fuites de mémoire — lors de la suppression par un pointeur de base, seul le destructeur de la classe de base était appelé, pas celui de la classe dérivée.
Histoire
Dans la classe, une méthode virtuelle a été déclarée, mais a été cachée par erreur dans l'héritier (pas
override, mais avec une autre signature/nom d'arguments). Les appels via la classe de base ne tombaient pas sur la fonction appropriée, ce qui entraînait un comportement incorrect.
Histoire
La mise à jour de la vtable après un héritage multiple a conduit à des erreurs : des appels incorrects de méthodes lors de l'utilisation de dynamic_cast, car le décalage du vptr dans une hiérarchie de classes complexe n'était pas pris en compte.