Skip to content

Module 9 — Composition and Association

After this module you can build a system out of cooperating objects, decide who owns what, and draw those decisions so that lifetime questions have answers before you write the code.


At a glance

Prerequisites Module 8
Estimated study time 7–9 hours
Lab Lab 9 — Library and Loan, with an ownership diagram
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

Real programs are not one class; they are many classes that know about each other. The relationships between them carry most of the design risk, because every relationship answers a question that the compiler will not ask for you:

Who owns this object? Who is responsible for it existing, and for it ceasing to exist?

Getting that wrong produces the classic C++ failures — a member destroyed before the object that still refers to it, a container holding copies it did not expect, two objects both believing they own the same memory, or a class whose internals are freely modifiable through a reference it handed out.

This module makes ownership explicit. You will use value members where a part genuinely belongs to its whole, references and pointers where it does not, and you will draw the relationships with ownership marked. Drawing forces the question; diagrams that ignore ownership hide exactly the bugs this module is about.

It is also where inheritance's limits become obvious. Most requirements that look like is-a turn out, on inspection, to be has-a — which is why the running system's Library is a composition of items, not a subclass of them.


Learning objectives

By the end of this module you will be able to:

  1. Express a has-a relationship with a member object, and explain what that member's lifetime is tied to.
  2. Distinguish composition (owns; exclusive; lifetime bound to the whole), aggregation (refers to; non-owning; may outlive the whole) and association (uses; a weaker, often temporary link).
  3. Choose between a value member, a reference member and a pointer member, and justify the choice in terms of ownership.
  4. Explain the construction and destruction order of member objects, and why it matters for invariants.
  5. Model a "many" relationship with a container member, and enforce an invariant that spans the whole collection.
  6. Prevent callers from mutating internals through a returned container reference, and offer query methods instead.
  7. Use a forward declaration to break an include cycle, and explain what a forward declaration does and does not allow.
  8. Add a forwarding method that delegates to a member without leaking its type.
  9. Draw a relationship diagram with ownership and multiplicity marked.
  10. Decide, with written reasoning, between inheritance and composition for a given requirement.

Core concepts

  • Composition — a part that is owned by its whole. Usually a value member, or a smart pointer. If the whole dies, the part dies.
  • Aggregation — a relationship to something that exists independently. A reference or non-owning pointer member, or a container of pointers.
  • Association — a general "knows about" link, often only for the duration of one operation (a function parameter).
  • Dependency — the weakest relationship: a type used in a parameter or return type and nothing more.
  • Ownership — the responsibility for an object's lifetime. Exactly one owner for exclusive resources; share only deliberately, and only from Module 11 onwards.
  • Multiplicity — one, optional-one, many. Expressed with a member, a pointer that may be null, or a container.
  • Value member — copied with the containing object and destroyed with it. The default and usually the right choice.
  • Reference member — must be initialised in the constructor and cannot be reseated. Makes the object non-assignable by default, which is often a useful signal.
  • Pointer member — nullable, re-seatable, and not owning unless it is a smart pointer.
  • Encapsulating a collection — keeping the container private and exposing queries. Returning std::vector<T>& hands callers the ability to break every invariant you just enforced.
  • Forward declaration — class Item; names a class without including its header, which breaks include cycles and reduces compile time. It is enough for references and pointers, but not for value members or for calling members.
  • Include cycle — two headers that include each other. Symptom: error: 'X' does not name a type in a file that clearly includes the right header.
  • Delegation / forwarding — a member function that calls the same operation on a member, so callers do not need to know the member's type.
  • Law of Demeter — talk to your immediate collaborators, not to their internals. a.getB().getC().doThing() couples three types into one line of code.

Module contents

Section What it contains
Introduction (this page) Overview, prerequisites, objectives, concept inventory
Concepts and explanations The four relationships and their UML notation; ownership; members as objects; value, pointer and reference members; exposing a collection; forward declarations and include cycles; composition over inheritance; dependency direction; the Law of Demeter
Worked examples Five programs — three relationships with counters that prove the lifetimes, a class built from member objects, four ways to depend on a logger, the accessor that gave two invariants away, and a queue that counts late submissions
Code examples examples/module-09/ — five programs, each with captured output
Misconceptions and mistakes Seven wrong mental models, ten symptoms, and five worked mistakes with their real compiler messages
Debugging The ownership map, the four silent failures, and a diagnosis that starts by asking whether you hold the object or a copy
Exercises Three tiers, including a design to audit for ownership and a class to break on purpose
Lab 9 Library and Loan: an ownership diagram as a deliverable, a broken cycle, a collection invariant, and a reallocation trap to find
Challenge problems An ownership bug to diagnose, a system with three lifetimes to design, and an evaluation of "composition over inheritance"
Self-assessment Ten questions, including three predictions and a design to correct
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

  • Draw a relationship diagram with ownership and multiplicity marked, and defend every arrow.
  • Choose member types on ownership grounds rather than convenience.
  • Break an include cycle with a forward declaration and explain the trade-off.
  • Stop a caller from mutating your internals through a returned collection.
  • Take a requirement written as inheritance and re-express it as composition when that is the better fit.

Where this is used later

  • Ownership becomes the subject of Module 10 and Module 11.
  • Library-style aggregates are what the generic container of Module 12 will hold.
  • Query methods over collections are implemented with the standard algorithms in Module 14.
  • Composition over inheritance is one of the arguments behind the design principles of Module 15.
  • Forward declarations and multi-file organisation are revisited at scale in Module 16.

Common mistakes to watch for

Symptom Cause
Members are destroyed in the wrong order, or a member points at something already gone A non-owning member was declared before the member it refers to; destruction runs in reverse declaration order
error: 'Item' does not name a type in a file that includes the right header An include cycle. Break it with a forward declaration
error: field 'item' has incomplete type A forward declaration cannot be used for a value member — include the full header, or hold a pointer
A caller adds or removes elements behind your back A std::vector& was returned from an accessor. Return a const reference or offer queries
error: use of deleted function on assignment The class has a reference member, so the implicitly generated assignment operator is deleted. Decide deliberately what assignment should mean
Two objects both delete the same pointer Both believed they owned the same resource. Ownership must be exclusive, or explicitly shared — Module 11

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 draw an ownership map with multiplicities and a lifetime sentence on every arrow — evidence: your Lab 9 diagram, and the refactor it caused
  • [ ] I can choose between a value, pointer and reference member on ownership grounds — evidence: W2's six decisions, with owns / nullable / reseatable answered for each
  • [ ] I can explain the deleted assignment operator a reference member causes, and what I chose to do about it — evidence: the message quoted, and your replacement operator or your stated reason for none
  • [ ] I can expose a collection without handing it over — evidence: C2's three interfaces and your chosen one, with the caller's need named
  • [ ] I can break and then fix an include cycle — evidence: the original error, the forward declaration, and the source file that includes both headers
  • [ ] I can re-express an inheritance relationship as composition and state what it cost — evidence: X2's before and after, with the forwarding counted
  • [ ] I can say which objects a class does not own, and what keeps them alive — evidence: the lifetime sentences in your Lab 9 diagram

Suggested next step

Previous: Module 8 — Abstract Classes · Next: Module 10 — Copying and Resource Management · All modules: Modules index

Module 10 is the direct consequence of this module's decisions. Once a class owns something that is not a plain value — a raw pointer, a file handle, a buffer — the copy constructor, the assignment operator and the destructor stop being machinery the compiler can write for you, and getting them wrong is a double free or a leak with no diagnostic at all.