Module 6 — Summary¶
The essentials¶
- Inheritance writes what is genuinely shared once. A derived object contains a
base subobject and inherits the base's interface; it does not contain a copy of the
base's code, and it cannot name the base's
privatemembers. "Present" and "reachable" are different words for a reason. - The inheritance mode restricts the world, not the derived class. A derived class
reaches the base's public and protected members in all three modes; what changes is
what the outside sees, and whether the is-a relationship exists at all. Absent a
mode,
classmeans private inheritance. publicmeans is-a;protectedandprivatemean built-using — and "built using" is usually clearer as a member.protectedaccess is a coupling decision with every derived class in every project that derives from you.- Construction goes base first, then members in declaration order, then the constructor body; destruction is the exact reverse. The initialiser list's text has no effect on that sequence — it supplies arguments, and the compiler warns when its order disagrees with the declarations. A base is always initialised first, no matter what the list says.
- A derived declaration hides every base function of that name, whatever the
signatures.
using Base::name;restores the whole overload set; qualifying a call fixes one call site. Hiding is not overriding, and overriding needsvirtualplus an identical signature. - Multiple inheritance gives one subobject per base, so a diamond gives two copies
of the shared ancestor and two answers to every question about it — with no
diagnostic at all.
virtualinheritance shares one subobject and is best reserved for interface-only diamonds; an association is the better answer for shared state. - Before inheriting, check every public operation of the base. One the derived type
cannot honour means it is not an is-a relationship, and a caller holding the base can
break the derived type's invariant through it.
FixedStackcomposes anIntList;BrokenStackinherits one and loses its capacity rule in three lines. - Inheriting implementation binds you to the base's internals forever. Inheriting
an interface is what buys extensibility — and, as Module 7 shows, only
virtualmakes that extensibility real.
The one-sentence version¶
Inherit only when the derived type genuinely is the base type for every operation the
base publishes; expect the base part to be built first and destroyed last; and remember
that without virtual, a call through a base reference runs the base's version, which
is the difference between sharing code and getting polymorphism.
Terms you must now be able to define¶
Base class · derived class · is-a versus has-a versus is-implemented-using ·
public / protected / private inheritance · protected member · base initialiser ·
construction order · destruction order · name hiding · using Base::name; · multiple
inheritance · the diamond problem · virtual inheritance · ambiguity · inheriting
constructors (using Base::Base;) · final class · fragile base class · object slicing
(preview) · Liskov substitution (preview)
See the glossary. Three matter most for what follows:
name hiding (Module 7's first trap, and the reason override exists), virtual
destructor (introduced as a rule here, explained as a bug there), and is-a versus
has-a (Module 9's whole subject).
The habit to keep¶
Draw the subobject map before you debug a hierarchy. Boxes, arrows, and one object written out in construction order. Two minutes with a pencil answers the three questions that account for nearly every hierarchy bug: how many copies of the shared base, which class's function runs, and what is used before it exists.
Second habit, once per hierarchy: audit the base's public operations. For each one, write "a caller could …" and finish the sentence. The operation you cannot finish is the one that decides whether this should be inheritance.
Before you move on¶
- [ ] Warm-up and core exercises attempted, with results checked
- [ ] Lab 6 builds with no warnings, and its predicted trace is in the deliverable
- [ ] Your relationship audit has at least two attacks that compile, not only attacks that do not
- [ ] You can write a three-level hierarchy and predict its construction and destruction order before running it
- [ ] You can explain, with the compiler's own wording, the difference between "private within this context" and "is an inaccessible base of"
- [ ] You can say what the inheritance mode does not change
- [ ] Self-assessment ≥ 80%, with question 8's two silent mistakes both named
- [ ] At least one challenge problem attempted, with the rubric applied
- [ ] The skills checklist on the module page ticked with evidence
Further reading¶
- Effective C++ (3rd ed.), Items 32, 34, 36, 38 and 39 — public inheritance means is-a; distinguish interface inheritance from implementation inheritance; never redefine an inherited non-virtual function; model has-a through composition; use private inheritance judiciously. Items 32 and 38 are this module's two halves.
- The C++ Programming Language (4th ed.), the class-hierarchy chapter — the language's own account of access modes, construction order and multiple inheritance, including the virtual-base rules this course uses but does not lean on.
- The C++ Standard, [class.derived], [class.base.init] and [class.mi] — the normative text for inheritance, initialisation order and multiple inheritance. Worth reading [class.base.init] once, because the ordering rules are stated more precisely there than anywhere else.
- C++ Core Guidelines —
C.120–C.137(class hierarchies; avoid protected data;virtualbases for interfaces only; useoverride/final),C.145(access polymorphic objects through pointers and references), andC.35–C.37(destructors and virtuality, which Module 7 completes). - Design Patterns (GoF), the "Favor object composition over class inheritance" paragraph in the introduction — five lines that this module's §8 spends a page demonstrating.
See Further Reading for editions and why these are the right ones.
Next: Module 7 — Polymorphism. Module 7 turns the hierarchies you have just built into something a caller can use without knowing which derived type it holds — and reproduces, deliberately, the three traps that catch almost everyone: slicing, the missing virtual destructor, and the virtual call that does not dispatch.