Inheritance & Polymorphism
Building on What Exists
Say you're building a drawing app. You have shapes β circles, rectangles, triangles. They all share some traits: a color, a position, an area() method. But each shape calculates area differently.
You could copy-paste the shared code into every shape class. Or you could write it once in a base class called Shape and let each specific shape inherit from it, adding only what makes it unique.
That's inheritance β a "is-a" relationship. A Circle is a Shape. A Rectangle is a Shape. They share the Shape interface but each has its own implementation.
Basic Inheritance β Shape Hierarchy
The Problem: What Happens Through a Base Pointer?
Here's where things get interesting β and tricky. If you store a Circle through a Shape* pointer and call area(), which version runs? Without the virtual keyword, it calls Shape's version β even though the actual object is a Circle. This is called static binding, and it's almost never what you want.
The fix? Make the base class method virtual. This tells the compiler: "Don't decide at compile time β check the actual object type at runtime." This is polymorphism.
virtual Functions β Polymorphism in Action
The override Keyword β Your Safety Net
Always use override when you intend to override a virtual function. Without it, a typo in the function signature silently creates a new function instead of overriding the base one. With override, the compiler catches the mistake.
// Without override β silent bug if signature doesn't match
double area() const { ... } // Might not actually override!
// With override β compiler error if it doesn't match a base virtual
double area() const override { ... } // Safe!Pure Virtual Functions & Abstract Classes
Sometimes a base class method has no reasonable default implementation. Every shape must have an area, but "Shape" by itself doesn't know how to calculate one. This is where pure virtual functions come in.
By writing = 0, you're saying: "I'm not implementing this β every derived class MUST." A class with at least one pure virtual function is abstract β you can't create instances of it directly.
Abstract Classes β Enforcing the Contract
Why Virtual Destructors Matter
This is one of the most important rules in C++, and ignoring it causes silent memory leaks.
When you delete an object through a base class pointer (delete basePtr;), the compiler needs to know which destructor to call. Without virtual, it only calls the base class destructor, skipping the derived class cleanup entirely.
Virtual Destructor β Preventing Memory Leaks
virtual ~Base() = default;The Slicing Problem
One last gotcha. When you assign a derived object to a base object by value, the derived parts get sliced off. Only the base portion remains. This is called object slicing, and it's a sneaky bug.
Object Slicing β A Subtle Bug
Key Takeaways
- Use
virtualon base class methods that derived classes should override - Always mark overrides with
overridefor compile-time safety - Use
= 0for pure virtual functions when there's no sensible default - If you have virtual functions, make the destructor virtual too
- Use pointers or references (not value types) to avoid object slicing
- Prefer
unique_ptrfor owning polymorphic objects