Skip to content

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:

  1. Declare a pure virtual function with = 0 and explain what that declaration means.
  2. Explain why a class with at least one pure virtual function is abstract, and why the compiler refuses to instantiate it.
  3. Distinguish an abstract base class (some behaviour, some pure virtual functions) from an interface (no data, all pure virtual functions).
  4. Provide a default implementation for a pure virtual function where that is genuinely useful, and call it explicitly from an override.
  5. Define the contract of an interface in words — preconditions, postconditions and invariants — and check an implementation against it.
  6. Give a polymorphic base class a virtual destructor, and explain why an abstract base always needs one.
  7. Prevent further overriding with final where an implementation must not be replaced.
  8. Explain why small, focused interfaces are preferable to large ones, with a concrete example from the running system.
  9. Recognise a hierarchy that is too deep, and describe two ways to flatten it.
  10. 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 IThing or ThingInterface; 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 as Base::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 type and find the missing override without opening a search box — evidence: the note: 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() or render()
  • [ ] 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.