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:
- Express a has-a relationship with a member object, and explain what that member's lifetime is tied to.
- 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).
- Choose between a value member, a reference member and a pointer member, and justify the choice in terms of ownership.
- Explain the construction and destruction order of member objects, and why it matters for invariants.
- Model a "many" relationship with a container member, and enforce an invariant that spans the whole collection.
- Prevent callers from mutating internals through a returned container reference, and offer query methods instead.
- Use a forward declaration to break an include cycle, and explain what a forward declaration does and does not allow.
- Add a forwarding method that delegates to a member without leaking its type.
- Draw a relationship diagram with ownership and multiplicity marked.
- 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
privateand exposing queries. Returningstd::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 typein 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.