How do virtual functions, vtables and virtual destructors work?
A virtual function enables runtime polymorphism. Each polymorphic class gets a vtable of function pointers and each object stores a hidden vptr. A call through a base pointer looks up the most-derived override at runtime.
struct Base {
virtual ~Base() = default;
virtual void speak() const { std::cout << "base"; }
};
struct Derived : Base {
void speak() const override { std::cout << "derived"; }
};
Base* p = new Derived();
p->speak(); // "derived"
delete p; // needs a virtual destructor
If the base destructor is not virtual, deleting through a base pointer is undefined behaviour and derived members are not destroyed, leaking resources. Any base intended for polymorphic deletion needs a virtual destructor.
Mark overrides with override so signature mismatches become compile errors, and use final to prevent further overriding and enable devirtualisation. Virtual calls cannot be inlined and add an indirection, so do not make everything virtual; keep interfaces small and stable.