Skip to content

Module 9 — Exercises

Every exercise states its level, its task, and the observable result that counts as success. If you cannot tell whether you succeeded without reading a solution, the exercise is defective — tell the course maintainers.

Solutions point at programs you have already been given. Attempt each exercise before you look.


Warm-up [L1 Understand]

W1 — name the relationship and its lifetime

Task. For each pair, say which of composition, aggregation, association or dependency it is, draw it in UML notation with multiplicity, and write the lifetime sentence ("the X dies with the Y" / "the X must outlive the Y"):

  1. Course / its enrolled Students
  2. Course / the Lecturer teaching it
  3. Course / the Department it belongs to
  4. Report / the Formatter used while rendering
  5. Library / its Loan records
  6. Loan / the Member who borrowed

Success looks like. Six classifications, six diagrams with a diamond or arrow, and six lifetime sentences. Any pair where you cannot write the sentence is a pair to look at again.

Stuck? Concepts §1 and §2.


W2 — value, pointer or reference?

Task. Six members, and for each one choose the member form and justify it in terms of owns / may be null / may be reseated:

1. A purchase order's list of line items
2. A session's logger, supplied by the caller, which must not be null
3. A player's current track, which changes when the user presses next
4. A parser's error reporter, needed only inside parse()
5. A student's programme of study, fixed at enrolment
6. A cache's eviction policy, which the configuration file chooses at start-up

Success looks like. Six choices with the three words answered for each. One of the six should be a parameter, and you should say why the other five are not.

Stuck? Concepts §4 has the decision table.


W3 — predict the destruction

Task. Given these declarations, write the destruction order of the members and the constructor order, then check with a program. Then move dependency_ to the first position and predict what changes.

class Engine {
    Part block_;
    Part head_;
    Part dependency_;      // declared last
};

Success looks like. Both orders written before running, plus the corrected version with a one-line explanation of which list was reversed. Any error is worth more than a lucky guess, so record it.

Stuck? Concepts §3, or extend examples/module-09/02-nested-objects-and-delegation/main.cpp.


Core [L2 Apply] · [L3 Modify] · [L4 Analyze]

C1 — build the three relationships [L2 Apply]

Task. Write a small system with one class of each kind: a Team that owns its Players (composition), holds a pointer to a Coach it did not create (aggregation), and takes a Stadium& it did not create and cannot change (association). Add counters, and print the state of all three after the Team is destroyed.

Success looks like. The counters show the players destroyed and the coach and stadium untouched; a const Team& can report but not modify; and you can point at the three declarations and say who deletes what.

Stuck? examples/module-09/01-three-kinds-of-relationship/main.cpp is the reference.


C2 — three ways to expose the same collection [L3 Modify]

Task. Take the Roster from examples/module-09/04-…/main.cpp and implement three interfaces for it in one program: (a) const std::vector<Student>& all() const, (b) begin()/end(), (c) forEach(callback). Write a caller for each, then answer: which one survives replacing the container with a std::list, and which one survives a caller needing to keep the data after the roster is gone?

Success looks like. Three callers in one program, a table of what each interface costs and buys, and one written paragraph choosing which you would publish — with the reason naming the caller's need, not your preference.

Stuck? Concepts §6.


C3 — break it on purpose, then close the door [L3 Modify]

Task. Write a class with two stated invariants (for example: a fixed maximum size, and no duplicate keys) and three public operations that can change its contents — one of which maintains the invariants and two of which do not. Print the state after each operation.

Then close the doors: reduce the interface so that only operations that maintain the invariants remain, and show that the two attacks no longer compile.

Success looks like. Both invariants observably broken by code that never mentions your class, then the reduced interface, with the exact sentence you would write in a code review for each removed operation.

Stuck? Concepts §6 and Mistake 1 in Misconceptions and mistakes.


C4 — break the cycle [L4 Analyze]

Task. Two classes need each other: a Loan records the Member who borrowed, and a Member records the current Loan. Start with both header files including each other, record the error, then fix it with a forward declaration plus a source file. State what the fix costs: which operations must now be implemented out of line, and why.

Success looks like. The original error recorded verbatim (including the In file included from lines), a working build afterwards, and the two-sentence explanation of what a forward declaration does and does not allow — with one example of each in your own code.

Stuck? Concepts §7; the messages are in Misconceptions and mistakes, Mistake 4.


Challenge [L5 Design] · [L7 Refactor]

X1 — audit a design you were handed [L4 Analyze]

Task. Below is an extract from a booking system's headers. Audit it and write the review you would hand back.

class Coach;                        // forward declaration
class Station;                      // forward declaration

class Booking {
public:
    Booking(std::string ref, Station& from, Station& to)
        : ref_(std::move(ref)), from_(from), to_(to) {}

    std::vector<std::string>& passengers() { return passengers_; }     // (1)
    Station& destination() { return to_; }                             // (2)
    void setCoach(Coach* coach) { coach_ = coach; }                    // (3)

private:
    std::string ref_;
    Station& from_;
    Station& to_;
    Coach* coach_ = nullptr;
    std::vector<std::string> passengers_;
};

Deliver: (a) an ownership map with multiplicities and a lifetime sentence per arrow; (b) one line per numbered operation saying what a caller can break, with a concrete caller that breaks it; (c) the smallest set of operations you would publish instead; (d) one defect the compiler will also complain about, and the message it produces.

Success looks like. Four labelled defects or justifications (some of the three may be acceptable — say why), two concrete attacks that compile, and a replacement interface that is smaller than the original. Requirement: (d) must mention that Booking is not assignable because of its reference members, and that the compiler deletes the operator rather than choosing a meaning.

How you will be judged. The rubric — the attacks are worth more than the verdicts.


X2 — re-express a requirement as composition [L7 Refactor]

Task. Take the queue from examples/module-09/05-extension-by-composition/main.cpp and replace its storage a second time — with a std::list<Submission> — while also adding an operation the container cannot express: earliestLate() returning the front-most late submission. Then do the same on a class of your own that currently inherits from a container (or from any type you do not control).

Deliver: the before and after, the diff between the two builds' outputs, the list of interface operations you chose to publish and the ones you deliberately did not, and one paragraph on what the refactor cost.

Success looks like. A third storage backend compiles without touching the class's interface; the diff shows changes only on lines that name the storage; and the paragraph names the forwarding cost honestly.


X3 — evaluate "composition over inheritance" [L8 Evaluate]

Task. The GoF sentence is quoted as a rule. Write an evaluation of it, using this course's own code as evidence: BrokenStack/FixedStack (Module 6), the item hierarchy (Modules 6–8), DeadlineQueue (Module 9), and any inheritance you would keep.

Cover: what composition buys (and what it costs in forwarding); where inheritance remains the right answer and why the GoF sentence is a default rather than a prohibition; and the case that is genuinely arguable — a small class with two subclasses you control, where the hierarchy is stable and the base is designed to be derived from.

Success looks like. A verdict with conditions, two worked cases from this repository, one place where you would keep inheritance, and the criteria you would apply in review — phrased so someone could apply them to a diff.

How you will be judged. Whether a reader who disagrees can follow the reasoning, and whether your criteria are checkable rather than aesthetic.


Stretch (optional)

  • S1 — "What changes if…" every non-owning pointer member in Example 1 became a reference member? List the classes that stop compiling, and what each one would have to give up.
  • S2 — "What changes if…" Roster::snapshot() returned std::vector<Student> by value but was called in a tight loop a million times? Measure it, then decide which access form the caller should have had.
  • S3 — "What changes if…" a Course held its Students as std::vector<Student*>, allocated in enrol()? Write the three lines that must be added, say where each belongs, and predict what happens if one is forgotten.

Solutions

Exercise Reference
W1, W2, C1 examples/module-09/01-three-kinds-of-relationship/main.cpp — counters that prove the three lifetimes
W3 examples/module-09/02-nested-objects-and-delegation/main.cpp — the same trace, with -Wreorder as the compiler's check
C2 examples/module-09/04-exposing-a-collection-safely/main.cpp — five access forms, plus the one that breaks everything
C3 Concepts §6, and Mistake 1 for the attack that compiles
C4 Concepts §7 and Mistake 4, with the cycle's real output
X1 The error table — (d) is the reference-member row
X2 examples/module-09/05-extension-by-composition/main.cpp — two backends, and the diff rule
X1, X3 (verdicts) No published answer: both are design arguments with more than one defensible verdict. The rubric is the marking scheme

X1's verdict and X3 have no published answer on purpose. Compare with the rubric after writing your own.


Next: Lab 9 — the running system becomes cooperating objects, with an ownership diagram as a deliverable. · All modules