Virtual functions, vtables, abstract base classes, pure virtual functions, the diamond problem, and the override/final specifiers.
The virtual keyword on a method ensures the compiler uses the most-derived overriding version when the function is called through a base pointer or reference. Without it, the base version is always called regardless of the actual object type.
class Fish {
public:
virtual void Swim(); // virtual — derived can override
};
class Tuna : public Fish {
public:
void Swim(); // overrides Fish::Swim
};
void Func(Fish& fishy) {
fishy.Swim(); // calls Tuna::Swim if a Tuna is passed
}
Fish* dinner = new Tuna();
dinner->Swim(); // calls Tuna::Swim — needs pointer for vtable to work
this pointer and are resolved at compile time, which is the opposite of what virtual dispatch does.The compiler implements virtual dispatch through a virtual function table (vtable). Here's what happens under the hood:
If you delete a derived object through a base pointer and the base destructor is not virtual, the derived portion may never be destroyed — causing a memory leak or undefined behavior:
Base* b = new Derived();
delete b; // calls Base::~Base only — Derived's destructor never runs!
Making the base destructor virtual fixes this — the vtable ensures the correct destructor chain is called:
class Base {
public:
virtual ~Base() {} // virtual — Derived's dtor will be called first
};
The vtable is not fully set up during construction or destruction. The base class is constructed before the derived class, so when the base constructor runs, the vptr points to the base vtable — not the derived one. Calling a virtual function there won't dispatch to the derived implementation.
class Base {
public:
Base() {
Init(); // calls Base::Init, NOT Derived::Init — even if overridden!
}
virtual void Init();
};
A pure virtual function is declared with = 0 and has no implementation in the base class. Any class with at least one pure virtual function becomes an abstract base class (ABC) — it cannot be instantiated directly.
class Shape {
public:
virtual void Draw() = 0; // pure virtual — Shape is now abstract
virtual ~Shape() {}
};
Shape s; // ERROR — cannot instantiate abstract class
Shape* s; // OK — pointer to abstract class is fine
Derived classes must implement all pure virtual functions to become concrete (instantiable). ABCs are essentially C++'s way of defining an interface.
When multiple inheritance creates a shared ancestor, the ancestor is included multiple times — once per path. This is the diamond problem:
class Parent { public: int age; };
class ChildA : public Parent {};
class ChildB : public Parent {};
class ChildC : public Parent {};
class GrandChild : public ChildA, public ChildB, public ChildC {};
GrandChild ends up with 3 copies of Parent — one per path. The constructor for Parent is called 3 times, and gp.age is ambiguous.
Mark the inheritance as virtual in the intermediate classes. This ensures only one shared copy of the common base is created:
class ChildA : public virtual Parent {};
class ChildB : public virtual Parent {};
class ChildC : public virtual Parent {};
class GrandChild : public ChildA, public ChildB, public ChildC {
// Now only one Parent subobject — no ambiguity
};
override & finaloverrideAdding override to a method signals your intent to override a virtual function. The compiler then enforces two checks:
virtualclass Tuna : public Fish {
public:
void Swim() override; // compiler verifies this actually overrides Fish::Swim
};
override on derived virtual functions. It turns silent bugs (wrong signature) into compiler errors.final on Methodsfinal can also be applied to a virtual method (not just a class) to prevent further derived classes from overriding it:
class Tuna : public Fish {
public:
void Swim() override final; // no class deriving from Tuna can override Swim
};
Virtual functions are dynamically bound — the derived implementation is called. But default parameter values are statically bound — they are resolved at compile time based on the declared type of the pointer/reference, not the actual object.
class Base {
public:
virtual void Log(int x = 10);
};
class Derived : public Base {
public:
void Log(int x = 99) override;
};
Base* b = new Derived();
b->Log(); // calls Derived::Log — but with x = 10 (Base's default)!