In C++, dynamic polymorphism is implemented through the mechanism of virtual functions using a special table called vtable (virtual table). For any class with at least one virtual function, the compiler generates a vtable: this is an array of pointers to the class's virtual functions. Each object of such a class stores a hidden pointer to the vtable — the so-called vptr (virtual pointer).
When an object of a class with virtual functions is created, the vptr is initialized to point to the vtable corresponding to the object's class. When a virtual function is called, the call occurs not directly, but through the address stored in the vtable, selecting the appropriate implementation based on the actual type of the object (even when accessed through a base type).
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(); // call via vtable }
The call to foo will refer to the vtable: if in Derived, the overridden function will be called.
"Is a method virtual if it is declared as
virtualbut not overridden in the derived class? Will it still work with calls through vtable?"
Answer: Yes, if a function is declared as virtual in the base class, it remains virtual for all descendants, even if not overridden. The call through the base pointer will go through the vtable anyway. If the method is not overridden, the version from the base class will be called.
Example:
struct Base { virtual void foo() { std::cout << "B "; } }; struct Derived : Base { }; Base* obj = new Derived(); obj->foo(); // Will output "B", but the call is via vtable!
Story
In a large project, the destructor was forgotten to be declared as virtual in the base class with virtual functions. This led to memory leaks — when deleting through the base pointer, only the base class destructor was called, not the derived one.
Story
A virtual method was declared in the class, which was mistakenly hidden in the derived class (not
override, but with a different signature/name of arguments). Calls through the base class did not reach the intended function, leading to incorrect behavior.
Story
Updating the vtable after multiple inheritance led to errors: incorrect method calls when using dynamic_cast, as the offset of vptr in the complex class hierarchy was not taken into account.