← C++, End to End

C++, End to End

Virtual Functions and Polymorphism

Inheritance alone gives you code reuse. Virtual functions give you runtime polymorphism: the ability to call a function through a base class pointer or reference and have the correct derived class version run, without the caller needing to know which derived type it actually has.

The problem virtual functions solve

c++ · live, editable, runnableOpen in Compiler Explorer ↗

Without virtual, which function runs is decided at compile time based on the static type of the reference/pointer (Shape&), not the actual object it refers to (a Circle). That's the bug above: through a Shape&, you always get Shape's area(), even though the object underneath is a Circle. Marking area() virtual fixes it: the call is resolved at runtime based on the object's actual type.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

How this actually works: vtables, conceptually

You don't need to know assembly to use virtual functions, but the mental model helps: a class with any virtual functions gets a hidden pointer to a vtable, a per-class table of function pointers for its virtual functions. Calling a virtual function through a pointer/reference means: look up the object's actual vtable (via that hidden pointer), find the entry for this function, call whatever it points to. That indirection is the entire mechanism, and it's also why virtual calls have a small but real runtime cost compared to a plain non-virtual call.

Pure virtual functions and abstract classes

A pure virtual function (= 0) has no implementation in the base class and forces every concrete derived class to provide one. A class with at least one pure virtual function is abstract: you cannot instantiate it directly, only through a derived class that implements all pure virtuals.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

override and final

Always write override on functions you intend to override. Without it, a typo (wrong parameter type, missing const, wrong name) silently creates a brand new, unrelated function instead of overriding anything, and the compiler won't warn you. With override, the same typo is a compile error, because the compiler can now check there's actually a matching virtual function in the base to override.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

final on a virtual function (or an entire class) prevents further overriding (or further inheritance). Use it when you have a specific reason to close off extension, not as a default habit.

Virtual destructors: the bug that looks fine until it isn't

If you delete a derived object through a base class pointer, and the base class destructor isn't virtual, only the base part gets destroyed. The derived part's destructor never runs. If the derived class owns any resources (heap memory, file handles), that's a leak, and it's undefined behavior even when nothing looks obviously wrong.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

Rule: any class you intend someone to inherit from, and delete through a base pointer, needs a virtual destructor. If a class isn't meant to be a polymorphic base at all, it doesn't need one, and forcing one on everything is unnecessary overhead.

Slicing: the other classic gotcha

Passing or assigning a derived object into something expecting a base object BY VALUE (not by reference or pointer) copies only the base part. The derived-specific data is silently sliced off, and any virtual dispatch is gone since you now have a genuine, separate Base object, not a Derived one viewed through a base reference.

c++ · live, editable, runnableOpen in Compiler Explorer ↗

The fix is always the same: take polymorphic objects by reference or pointer, never by value, if you want virtual dispatch to actually work.

Try it yourself: build a small polymorphic hierarchy

Write an abstract Shape with a pure virtual area() and a virtual destructor, then Rectangle and Triangle derived classes. Store several of them (as std::unique_ptr<Shape>, so ownership is clear) in a std::vector and print all their areas through a loop over base class pointers.

c++ · live, editable, runnableOpen in Compiler Explorer ↗