Skip to content

Module 9 — Summary

The essentials

  • Every have-a relationship answers one question: who deletes it, and when? Composition (owns), aggregation (refers, does not own), association (knows), dependency (uses once). Write the lifetime sentence for each non-owning arrow before you write the code — the arrow without a sentence is the bug you will meet in six months.
  • The member's form is the relationship. Value member = owns, cannot be null, copied with the object. T* = borrows, may be null, may be reseated. T& = borrows, never null, never reseated — and the class loses its assignment operator. A parameter = no lifetime question at all. Prefer the weakest true statement.
  • A reference member's price is stated by the compiler: use of deleted function and non-static reference member … cannot use default assignment operator. It cannot be bound to a temporary either. Both are the language refusing to let the declaration lie.
  • Members are constructed in declaration order before the outer body, and destroyed in the reverse order after it. const propagates through delegation for free: render() const can call only const member functions, so a const object cannot reach a mutator.
  • Expose the capability, not the container. const& binds callers to the type; begin()/end() gives read-only traversal; snapshot() gives independence at the cost of a copy and staleness; forEach gives the data without naming any type. A non-const container reference hands over every invariant the class enforces — as Example 4 measures.
  • A forward declaration names a type; it does not define it. Enough for pointers and references; not enough for a value member (field 'x' has incomplete type) or for calling a member function (invalid use of incomplete type). Headers declare; source files include.
  • An include cycle is one header including another that includes it. The error appears in a file that includes the right header, and the In file included from lines are the cycle printed as a stack.
  • Composition is how you extend a type you do not own. Inheriting from std::vector or std::deque is not available: no virtual destructor, every container operation published on your type, and every caller bound to the representation. Forwarding is the cost, and it is small; the payload is a small interface and a swappable representation.
  • Talk to your immediate collaborators. a.b().c().d() couples three types into one line; the fix is usually a missing operation on a, not a style change.

The one-sentence version

Decide who owns each object and write it down — as a value member when you own it, a pointer when it may be absent, a reference when the relationship is fixed, a parameter when it is momentary — expose capabilities rather than containers, and never inherit from a type you do not control unless every one of its operations stays true of yours.

Terms you must now be able to define

Composition · aggregation · association · dependency · multiplicity · value member · pointer member · reference member · owning versus non-owning · lifetime requirement · delegation · forwarding · forward declaration · incomplete type · include cycle · include guard · exposing a collection · snapshot · Law of Demeter · dependency injection · composition over inheritance

See the glossary. Three matter most for what follows: ownership is the whole of Module 10 and Module 11; lifetime requirement is what weak_ptr formalises; and dependency injection returns as the dependency-inversion principle in Module 15.

The habit to keep

Draw the ownership map before you write the class — box per type, arrow per relationship, multiplicity on each end, and one lifetime sentence per non-owning arrow. It takes five minutes and it answers the questions the compiler will not: who deletes this, how long does the pointer stay valid, and what happens when the owner dies first.

Second habit: enumerate the doors. When you state an invariant, list every operation that can change the object — including constructors, friends, accessors that return references, and anything it inherits. A rule enforced in one function holds only if that function is the only way in.

Before you move on

  • [ ] Warm-up and core exercises attempted, with results checked
  • [ ] Lab 9 builds with no warnings, its ownership diagram has lifetimes on every arrow, and its counters agree
  • [ ] You can write the three member forms from memory and say what each promises
  • [ ] You have seen the deleted-assignment message, and can explain it in one sentence
  • [ ] You have broken an invariant through an accessor on purpose, then removed the accessor
  • [ ] You have broken an include cycle with a forward declaration and can say what it costs
  • [ ] Self-assessment ≥ 80%, with question 9 answered as two separate lifetime problems
  • [ ] 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 13, 14, 38 and 39 — manage resources through objects, think carefully about copying behaviour, model has-a through composition (Item 38 is this module's centrepiece), and use private inheritance judiciously.
  • *Effective Modern C++, Items 18–20 — unique_ptr, shared_ptr and weak_ptr as the mechanical form of the ownership decisions made here. Read them with Module 11.
  • The C++ Programming Language (4th ed.), the chapters on classes and on resource management — member lifetimes, references versus pointers, and the language's own statement that a reference member makes a class non-assignable.
  • Design Patterns (GoF), "Favor object composition over class inheritance", and the Composite, Adapter and Observer patterns — all four are composition relationships with different policies, and Module 15 revisits them.
  • C++ Core Guidelines — C.12 (don't make data members const if the class needs assignment), C.30–C.32 and C.35 (access, ownership and destruction), C.128, and I.11/F.15 on parameter passing. R.3–R.4 on references versus pointers is also short and directly relevant.
  • Clean Code (Martin), the Law of Demeter discussion — a chapter of opinionated rules around this module's §10, useful precisely because you will want to disagree with part of it.

See Further Reading for editions and why these are the right ones.


Next: Module 10 — Copying and Resource Management. Module 10 is the direct consequence of every decision made here: once a class owns something that is not a value, the copy constructor, the assignment operator and the destructor stop being things the compiler can write for you — and getting them wrong is a double free or a leak, with no diagnostic at all.