Module 6 — 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, so that "checking your answer" means comparing your behaviour with a program that was verified on this toolchain. Attempt each exercise before you look.
Warm-up [L1 Understand]¶
W1 — classify the relationships¶
Task. For each pair, decide whether it should be inheritance or a member, and say
which word decides it (is-a, has-a, is-implemented-using):
Manager/EmployeeCatalogue/std::vector<Book>FixedStack/IntListCircle/ShapeCar/EngineSavingsAccount/AccountProxyLogger/FileLogger(a logger that forwards after filtering)TeachingAssistant/Student,Tutor
Success looks like. Eight verdicts, each with the deciding word and — for the two that are arguable — one sentence saying what additional fact would change your mind.
Stuck? Concepts §8.
W2 — read a diagram back into code¶
Task. Each of these is-a / has-a mixtures is stated in words. Draw the UML
diagram (boxes with - # +, arrows, and 1..* multiplicities) and label each
arrow:
A library has many items. An item has an identifier and a title.
A book is an item and adds an author. A DVD is an item and adds a running time.
A member may borrow many items. Each loan is one member and one item.
Success looks like. One diagram, five boxes, every arrow labelled, and no arrow whose label you cannot defend in a sentence.
Stuck? Concepts §1 has the notation; Module 9 has the multiplicity forms, so a comment saying "multiplicity: Module 9" is a fine answer here.
W3 — predict the trace¶
Task. Before running anything, write the order of these lines for
class A { Tracer a1_, a2_; }; // declared a1_ then a2_
class B : public A { Tracer b_; };
class C : public B { Tracer c1_, c2_; };
C object;
in two cases: (a) as written; (b) with c2_ and c1_ swapped in the declaration.
Success looks like. Both orders written down, then checked by adding Tracers to
a scratch program and running it. A prediction that was wrong is worth more here than
one that was right — write the correction next to it.
Stuck? Concepts §4, or run
examples/module-06/03-construction-and-destruction-order/main.cpp and extend it.
Core [L2 Apply] · [L3 Modify] · [L4 Analyze]¶
C1 — build the payroll hierarchy yourself [L2 Apply]¶
Task. Write, from scratch and without looking, three classes:
Employee (id, name, monthly paisa; private data, a protected setMonthlyPaisa, a
public describe()), SalariedEmployee (adds a grade; its pay is base + grade ×
250000) and Contractor (adds a day rate and a days-per-month count; its pay is the
product, and it must be updated when the days change).
Requirements: a main that prints each employee's describe() and pay; a free
function taking const Employee&; no virtual anywhere.
Success looks like. It compiles with g++ -std=c++17 -Wall -Wextra -Werror, the
three pays are right, and you can point at the one line where the free function's
output is not what a polymorphic design would give — and explain why.
Stuck? examples/module-06/01-payroll-hierarchy/main.cpp is the reference; try
your own version first, then compare.
C2 — four broken hierarchies [L3 Modify]¶
Task. Each snippet is broken. Say what the compiler will report before you compile it, then fix each one, then compile to check yourself.
// (a)
class Base { public: explicit Base(int n) : n_(n) {} private: int n_; };
class D : public Base { public: D() {} };
// (b)
class D : Base { }; // Base has a public default constructor
// (c)
class P { public: void f(int); };
class Q : public P { public: void f(std::string); };
void use(Q& q) { q.f(3); }
// (d)
class Person { public: std::string name() const; };
class Student : public virtual Person { };
class Tutor : public virtual Person { };
class TA : public Student, public Tutor { };
void use(TA& ta) { std::cout << ta.name(); }
Success looks like. Four predictions, four fixes, four compiles. For (b) and (d)
your fix must be a design change with a one-line justification, not just the
minimum edit that silences the compiler — for (d), say whether the one shared
Person is what you actually wanted for a teaching assistant who is also an
employee.
Stuck? Misconceptions and mistakes has the verbatim messages for (a), (b) and (d).
C3 — count the subobjects [L4 Analyze]¶
Task. For each class below, state how many distinct std::string members an
object contains, then verify with a program that renames one and prints the others
through every qualified path available:
class Person { std::string name_; };
class Student : public Person {};
class Tutor : public Person {};
class TA : public Student, public Tutor {};
class TA2 : public virtual Student, public virtual Tutor {}; // with virtual bases
Success looks like. Numbers predicted first; a program that prints three names
for TA and one shared name for TA2; and a written explanation of which subobject
each accessor reaches. Note that TA2 requires virtual on the intermediate
bases, not only on the shared one — say why in one line.
Stuck? Concepts §7 and
examples/module-06/04-hiding-ambiguity-and-the-diamond/main.cpp.
C4 — audit someone else's hierarchy [L4 Analyze]¶
Task. Take the BrokenStack / IntList pair from
examples/module-06/05-composition-over-inheritance/main.cpp and write a
three-column audit: every public operation of IntList, whether it is true of a
stack, and what a caller could do with it that breaks the capacity rule. Then do the
same audit for one hierarchy of your own from C1 — this is the habit the module is
really teaching.
Success looks like. A complete audit (all six IntList operations), at least
three attacks that compile, and the sentence "the derived type cannot honour
…, therefore this is not an is-a relationship" written about the operation that
decides it.
Stuck? Concepts §8.
Challenge [L5 Design] · [L7 Refactor]¶
X1 — design the catalogue's item family [L5 Design]¶
Task. The campus catalogue will hold books, journals, DVDs and — from next year — e-resources with a URL and an access expiry. Design the class hierarchy.
Deliver: (a) a UML diagram; (b) the base class's interface with access levels; (c) a written paragraph naming every operation of the base and stating why it is true of all four kinds; (d) one candidate operation that you deliberately did not put in the base, with the reason; (e) the sentence saying whether the hierarchy could survive being flattened to one class with a kind flag, and why you rejected or accepted that.
Success looks like. A base with no member that is meaningless for one of its four
derived types — the payroll example's Contractor, built with monthlyPaisa = 0
because a contractor has no monthly salary, is the counter-example you must avoid or
justify. (Module 8 replaces that smell with an interface; a design that anticipates
it is a better answer than one that does not.)
How you will be judged. The rubric — the reasoning counts as much as the diagram.
X2 — refactor an inheritance design into composition [L7 Refactor]¶
Task. Find or write a hierarchy in which a derived class inherits an
implementation it does not fully honour. (BrokenStack : public IntList is the
worked case; a FixedArray : public std::vector<int> of your own is a good second.)
Refactor it to composition, preserving every capability its callers actually use.
Deliver: the before and after code; a list of call sites that no longer compile, with the sentence "this call was never valid for the type, because …" for each; the number of public operations before and after; and one paragraph on what your callers lost, if anything.
Success looks like. Every capability preserved, the interface smaller, and at least one call site whose removal you can justify as a bug fix rather than an inconvenience.
X3 — evaluate a five-level hierarchy [L8 Evaluate]¶
Task. Take a hierarchy with at least four levels — the standard library's stream
classes (std::ios_base → std::basic_ios → std::basic_ostream → std::ostream →
std::ofstream) count, and so does one of your own. Assess it against three criteria:
(i) can every derived class honour every inherited operation; (ii) how deep is
the coupling between a change at the root and code at the leaves; (iii) is any
level present only to reuse code rather than to express a relationship?
Success looks like. A written assessment with a verdict — keep, flatten, or replace with composition — and, for each criterion, one concrete observation about the hierarchy you chose rather than a general opinion. Name the pull towards "deep is bad" and say where it is wrong.
How you will be judged. Whether someone who disagrees with your verdict can still follow your reasoning, and whether you named what would change your mind.
Stretch (optional)¶
- S1 — "What changes if…" the base class has a default constructor: which of this module's errors disappear, and which of them become silent bugs instead?
- S2 — "What changes if…" you add
virtualtoEmployee::monthlyPaybut leave everything else alone? Predict, run, then check your prediction against Module 7's first example. - S3 — "What changes if…"
SalariedEmployee's constructor initialisesgrade_before callingEmployee(...)? Write the error, then explain why the language cannot allow it.
Solutions¶
| Exercise | Reference |
|---|---|
| W1, C1 | examples/module-06/01-payroll-hierarchy/main.cpp — a base with genuinely shared state, a protected setter, and the honest printout of what is not yet polymorphic |
| W2 | Concepts §1 for the notation; Module 9 for multiplicities |
| W3 | examples/module-06/03-construction-and-destruction-order/main.cpp — the trace, including the warning for a wrong initialiser order |
| C2 (a), (b) | Misconceptions, Part 2, mistakes 1 and 3, with the verbatim messages |
| C2 (c), (d) | examples/module-06/04-hiding-ambiguity-and-the-diamond/main.cpp — both cases, including the diamond that produces no diagnostic |
| C3 | The same file, §3 of its output; §7 of Concepts for virtual inheritance |
| C4, X2 | examples/module-06/05-composition-over-inheritance/main.cpp — the audit's facts are all in its output |
| X1 | No published answer. It is a design problem with more than one defensible answer, and the rubric is the marking scheme |
| X3 | No published answer: the assessment is the exercise, and the standard library hierarchy is one you can read for yourself |
X1 and X3 have no published answer on purpose. Compare with the rubric after writing your own.
Next: Lab 6 — the catalogue grows a family of items, and you must predict the construction trace of a three-level hierarchy before you are allowed to run it. · All modules