← C++, End to End

C++, End to End

Inheritance

Inheritance lets one class (the derived class) reuse and extend another (the base class). Done well, it models a real is-a relationship: a Circle is-a Shape, an Employee is-a Person. Done badly, it's used purely for code reuse between classes that don't actually belong in the same hierarchy, which is the single most common inheritance mistake in real codebases.

The basics

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

Notice the construction/destruction order in the output: base class constructor runs first, then derived. Destruction is the exact reverse: derived destructor first, then base. This makes sense once you think about it: a Dog can't finish constructing until the Animal part of it exists, and you shouldn't tear down the Animal part while the Dog part might still need it.

protected access

protected members are visible to the class itself and to derived classes, but not to outside code, the same as private for everyone except your own subclasses. It's a middle ground: use it when a base class wants to let derived classes reach into its internals directly, but still hide those internals from the rest of the program.

Overriding versus hiding

If a derived class defines a member function with the exact same signature as a virtual function in the base, it overrides it: calling that function through a base pointer/reference dispatches to the derived version (more on this in the next chapter on virtual functions). If the base function is NOT virtual, or the signatures don't actually match (a common typo trap: different parameter types, or forgetting const), the derived function just hides the base one instead of overriding it, and you get surprising, non-polymorphic behavior. Always use the override keyword (covered in depth next chapter) so the compiler catches signature mismatches for you instead of silently hiding.

Multiple inheritance and the diamond problem

C++ allows a class to inherit from more than one base class. This is used far less than single inheritance, and for good reason: if two base classes both inherit from a common ancestor, a derived class inheriting from both ends up with two copies of that ancestor's data, the classic diamond problem.

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

C++ has virtual inheritance to solve this (a single shared Base subobject instead of two), but the honest advice is: avoid multiple inheritance of implementation entirely where you can. Multiple inheritance of pure interfaces (abstract classes with no data, covered in the next chapter) is much safer and common in real code; multiple inheritance of classes that actually hold state is where the trouble starts.

Inheritance for is-a, not for convenience

The test from the previous chapter still applies here: can you truthfully say "a Derived is a Base" in every context the code will run in? A Stack that inherits from std::vector purely to reuse push_back is a classic anti-pattern: a Stack is not a vector (a vector lets you insert/remove anywhere; a stack shouldn't), and now every vector operation, including the ones that break the stack's invariants, is exposed on your Stack type. If you want the plumbing without the is-a contract, that's exactly what composition (previous chapter) is for.

Try it yourself: trace the construction/destruction order

Add a third level to the hierarchy above (a Puppy that inherits from Dog) and predict the full construction and destruction output before running it. Then check your prediction.

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