Module 8 — Abstract Classes¶
After this module you can define an interface that callers code against and that no one can instantiate wrongly — and you can keep hierarchies shallow instead of letting them grow out of control.
At a glance¶
| Prerequisites | Module 7 |
| Estimated study time | 6–8 hours |
| Lab | Lab 8 — abstract interface and per-kind policies |
| Exercises | Three tiers — warm-up, core, challenge |
| Cognitive levels | L4 Analyze → L6 Build — exit level L5 Design |
| Self-assessment | Module quiz |
Why this module matters¶
Once a class has a pure virtual function, it becomes something more useful than a base class: it becomes a contract. "Anything that is a lendable item must be able to report its identifier and compute its overdue charge" is now a statement the compiler enforces, and the compiler will refuse to let you create a half-finished object.
That refusal is the point. An abstract class cannot be instantiated, so there is
no such thing as a Shape that does not know how to draw itself. Every object you
hold through that interface has committed to the full contract.
This module also covers the discipline that keeps hierarchies healthy. Deep inheritance trees are easy to start and expensive to live with: each level adds assumptions that the level below inherits. You will learn to keep interfaces small, to prefer several narrow interfaces over one wide one, and to notice when a hierarchy is really a set of unrelated behaviours that happen to be spelled with inheritance.
Learning objectives¶
By the end of this module you will be able to:
- Declare a pure virtual function with
= 0and explain what that declaration means. - Explain why a class with at least one pure virtual function is abstract, and why the compiler refuses to instantiate it.
- Distinguish an abstract base class (some behaviour, some pure virtual functions) from an interface (no data, all pure virtual functions).
- Provide a default implementation for a pure virtual function where that is genuinely useful, and call it explicitly from an override.
- Define the contract of an interface in words — preconditions, postconditions and invariants — and check an implementation against it.
- Give a polymorphic base class a virtual destructor, and explain why an abstract base always needs one.
- Prevent further overriding with
finalwhere an implementation must not be replaced. - Explain why small, focused interfaces are preferable to large ones, with a concrete example from the running system.
- Recognise a hierarchy that is too deep, and describe two ways to flatten it.
- Explain why inheriting implementation rather than interface is the more dangerous kind of reuse.
Core concepts¶
- Pure virtual function — declared with
= 0; it has no (or a default) implementation and must be overridden by a concrete derived class. - Abstract class — a class with at least one pure virtual function. It cannot be instantiated, and cannot be passed by value.
- Concrete class — a derived class that overrides every inherited pure virtual function, and can therefore be instantiated.
- Interface — an abstract class with no data members and only pure virtual
functions (plus a virtual destructor). Many C++ programmers spell this
IThingorThingInterface; this course uses a clear suffix rather than a Hungarian-style prefix. - Design by contract — stating what a function requires of its caller (preconditions) and what it guarantees on return (postconditions), then implementing to that statement.
- Default (but still pure) virtual function —
virtual void f() = 0;may still be defined; derived classes must override it, but may call the base version explicitly asBase::f(). - Virtual destructor — mandatory for any polymorphic base that will be deleted through a base pointer. An abstract base is normally exactly that case.
final— locks an implementation so that no further class can replace it.- Interface segregation — preferring several narrow interfaces to one broad one, so a class is not forced to implement operations it cannot support. Named as a SOLID principle in Module 15.
- Fragile base class — the deeper the hierarchy, the more a change near the root can break. Shallow hierarchies with small interfaces are the defence.
- Implementation inheritance — inheriting behaviour you do not want, simply to reuse code. Usually a mistake; composition is the alternative (Module 9).
- Non-virtual interface (NVI) — a public non-virtual function that calls a private virtual one, so the base controls the surrounding logic. A technique worth recognising, and closely related to the template-method pattern in Module 15.
Module contents¶
| Section | What it contains |
|---|---|
| Introduction (this page) | Overview, prerequisites, objectives, concept inventory |
| Concepts and explanations | Pure virtual functions and abstractness, design by contract, interfaces versus abstract base classes, pure virtual functions with bodies, virtual destructors, interface width, hierarchy depth, and what the abstraction costs |
| Worked examples | Five programs — a contract and the loop it makes possible, a one-function interface with a test double, a payment contract that includes failure, abstract base and interface side by side, and a pure virtual function with a body |
| Code examples | examples/module-08/ — five programs, each with captured output |
| Misconceptions and mistakes | Seven wrong mental models, nine symptoms, and five worked mistakes with their real compiler and linker messages |
| Debugging | The contract audit, the abstractness audit, the four failure modes, and a diagnosis that starts from the interface |
| Exercises | Three tiers, including six snippets to predict before compiling and a wide interface to split |
| Lab 8 | The catalogue's contract, a replaceable loan policy, and a contract audit that must change something |
| Challenge problems | Six interfaces with one dishonest function each, a gradebook read side, and an evaluation of "program to an interface" |
| Self-assessment | Ten questions, including two compile predictions and an abstraction with four defects |
| Summary | What must be retained, the terms to be able to define, and the checklist |
| Further reading | Books and official documentation |
What you can do after this module¶
- Define an interface, write its contract in one paragraph, and implement it more than once.
- Explain why instantiation of an abstract class is a compile error, and why that is helpful rather than annoying.
- Delete derived objects safely through a base pointer.
- Take a deep hierarchy and flatten it without losing behaviour.
- Decide when to give a virtual function a default implementation and when that is a trap.
Where this is used later¶
- Contracts and interfaces are the vocabulary of the SOLID principles in Module 15, especially interface segregation and dependency inversion.
- Polymorphic ownership through abstract bases is Module 11.
- Default implementations and the NVI technique become the strategy and template-method patterns in Module 15.
- The interface designed here is what the capstone (Module 16) is written against.
Common mistakes to watch for¶
| Symptom | Cause |
|---|---|
error: cannot declare variable 'x' to be of abstract type 'Shape' |
One or more pure virtual functions is still not overridden in this class |
error: 'Shape' is an abstract class when passing by value |
An abstract class cannot be copied or passed by value; use a reference or a pointer |
error: invalid new-expression of abstract class type |
new-ing an abstract type, or a derived class that has not overridden everything |
| Objects deleted through a base pointer leak or crash | The base destructor is not virtual |
| Compiler error that an override "does not override" | Signature mismatch — most often a missing const |
| A derived class implements an operation it cannot really support | The interface is too wide. Split it |
| Every new feature adds a new level to the hierarchy | Design pressure in the wrong place: prefer composition, or a strategy object |
Skills checklist¶
Levels this module works at: [L4 Analyze → L6 Build] — see How you will learn.
Tick an item only when the evidence exists:
- [ ] I can write an abstract base class and an interface from memory, and say which I chose and what the choice gave up — evidence: Lab 8's design note
- [ ] I can read
cannot declare variable … to be of abstract typeand find the missing override without opening a search box — evidence: thenote:lines quoted in your own words - [ ] I can write a contract in four lines — precondition, both postconditions, invariant — and test the failure path — evidence: Lab 8's contract and its refusal branch
- [ ] I can audit an interface for functions an implementation cannot honour, and change the design as a result — evidence: the audit table with at least one row that moved a function
- [ ] I can explain why a pure virtual destructor must be defined, and what its absence looks like on this toolchain — evidence: the link error you produced and recorded
- [ ] I can say why a non-virtual public function calling protected virtuals is stronger
than making it virtual — evidence: your explanation of
describe()orrender() - [ ] I can name a type in this course that should not be abstracted, with a reason — evidence: your X3 evaluation, with its conditions
Suggested next step¶
Previous: Module 7 — Polymorphism · Next: Module 9 — Composition and Association · All modules: Modules index
Module 9 takes the other half of object-oriented design. Modules 6–8 built families whose members are kinds of one another; Module 9 is about objects that are made of other objects — what owns what, how long it lives, how two classes that refer to each other survive a header cycle, and how to expose a collection without exposing its representation. It is also where this module's open ownership question finally gets an answer.