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"):
Course/ its enrolledStudentsCourse/ theLecturerteaching itCourse/ theDepartmentit belongs toReport/ theFormatterused while renderingLibrary/ itsLoanrecordsLoan/ theMemberwho 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.
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()returnedstd::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
Courseheld itsStudents asstd::vector<Student*>, allocated inenrol()? 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