В C++ динамический полиморфизм реализуется через механизм виртуальных функций с помощью специальной таблицы — vtable (virtual table). Для любого класса с хотя бы одной виртуальной функцией компилятор генерирует vtable: это массив указателей на виртуальные функции класса. Каждый объект такого класса хранит скрытый указатель на vtable — так называемый vptr (virtual pointer).
В момент создания объекта класса с виртуальными функциями vptr инициализируется на таблицу vtable, соответствующую классу объекта. При вызове виртуальной функции вызов происходит не напрямую, а через адрес, хранящийся в vtable, выбирая нужную реализацию в зависимости от реального типа объекта (даже при обращении по базовому типу).
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(); // вызов через vtable }
Вызов foo будет обращением к vtable: если в Derived, то вызовется переопределённая функция.
"Является ли метод виртуальным, если он объявлен как
virtual, но не переопределён в производном классе? Будет ли в этом случае работать вызов через vtable?"
Ответ: Да, если функция объявлена как virtual в базовом классе, она остаётся виртуальной для всех потомков, даже если не переопределена. Вызов через базовый указатель в любом случае пойдёт через vtable. Если метод не переопределён — вызовется версия из базового класса.
Пример:
struct Base { virtual void foo() { std::cout << "B "; } }; struct Derived : Base { }; Base* obj = new Derived(); obj->foo(); // Выведет "B", но вызов через vtable!
История
На большом проекте забыли объявить деструктор как виртуальный в базовом классе с виртуальными функциями. Это привело к утечкам памяти — при удалении по базовому указателю вызывался только деструктор базового класса, не потомка.
История
В классе был объявлен виртуальный метод, который по ошибке был спрятан в наследнике (не
override, а с другим сигнатурой/именем аргументов). Вызовы через базовый класс не попадали на нужную функцию, что приводило к некорректной работе.
История
Актуализация vtable после множественного наследования привела к ошибкам: неверные вызовы методов при использовании dynamic_cast, т.к. не учитывалось смещение vptr в сложной иерархии классов.