Inheritance
Lesson outline
Fundamentals of Inheritance
Inheritance is an object-oriented feature that lets a new class (derived) reuse and extend the data and behavior of an existing class (base). The derived class inherits what is common and can add or customize features of its own.
In C++, inheritance primarily supports:
- Code Reusability: Share common functionality without duplication
- Class Hierarchy: Organize related types into logical families
- Polymorphism: Enable dynamic dispatch via virtual functions
Syntax uses a colon after the derived class name, followed by an access specifier (public/protected/private) and the base class name.
Default Inheritance Access (class vs struct)
If no access specifier is provided after the colon, classes inherit privately by default, while structs inherit publicly by default.
Member default access also differs: class members are private by default; struct members are public by default.
Access Control Deep Dive
C++ inheritance access specifiers affect the visibility of inherited members in the derived class and to clients using a derived object:
- public: Keeps base public members public and base protected members protected
- protected: Turns base public into protected; protected remain protected
- private: Turns base public and protected into private
Base private members are never directly accessible in the derived class (they still exist and can be accessed via base public/protected interfaces).
Upcasting rules: An implicit conversion from Derived to Base (viewing a derived object as its base) is public only with public inheritance; with protected/private inheritance, such conversions are not accessible to ordinary client code.
| Base Member | Public Inheritance | Protected Inheritance | Private Inheritance |
|---|---|---|---|
| private | Inaccessible | Inaccessible | Inaccessible |
| protected | Protected | Protected | Private |
| public | Public | Protected | Private |
Construction/Destruction Sequence
Object lifetime in an inheritance chain follows a strict order:
- Construction: Base subobjects are constructed first, then derived (top-down).
- Destruction: Derived is destroyed first, then base (bottom-up).
Use virtual destructors in polymorphic base classes to ensure correct cleanup when deleting through a base pointer.
Method Overriding & Virtual Functions
Virtual functions enable runtime polymorphism. Mark overrides explicitly with override to catch signature mismatches.
Key points:
- The base function must be virtual (or override/final) to dispatch dynamically.
- The overriding function must match the base signature, including const/ref/noexcept and reference qualifiers; return types may be covariant.
- Access control (
public/protected/private) is not part of the signature; a derived override may change accessibility (e.g., make a protected virtual public). - Use
finalto prevent further overriding when appropriate.
Name Hiding vs. Overriding
Introducing a function with the same name in a derived class hides all overloads of that name from the base (even if the signatures differ). Use using Base::func; to bring base overloads into scope when you want overloading rather than hiding.
Multiple Inheritance & Virtual Inheritance
C++ supports multiple inheritance. When two paths inherit the same base (the "diamond" problem), virtual inheritance ensures only one shared base subobject exists.
Use virtual inheritance when a common base must not be duplicated in derived-most objects.
Advanced Topics
Abstract Classes: Have at least one pure virtual function (= 0) and cannot be instantiated.
Interface Classes: Abstract classes that contain only pure virtual functions and no data; model contracts.
RTTI: Use dynamic_cast and typeid for safe downcasting in polymorphic hierarchies.
CRTP: Curiously Recurring Template Pattern for static (compile-time) polymorphism without virtual dispatch.
Best Practices Checklist
- □ Declare a virtual destructor in any class intended for polymorphic use.
- □ Prefer public inheritance to model 'is-a' interfaces; otherwise consider composition.
- □ Mark every intended override with
override; usefinalto stop further overrides. - □ Keep hierarchies shallow and coherent; avoid deep and wide inheritance trees.
- □ Clearly document the role (interface vs implementation) of each base class.
- □ Use composition for code reuse when an 'is-a' relationship does not hold.
- □ Define abstract bases (pure virtual) to express clear contracts.
- □ Avoid name hiding: use
usingto import base overloads when needed. - □ Avoid object slicing: pass and store polymorphic objects by reference or pointer, not by value.