Module 7 — 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: recognising an answer later is not the same as producing it now.
Warm-up [L1 Understand]¶
W1 — which calls dispatch?¶
Task. For each call, say whether it is statically or dynamically bound, and name the
function that runs. Assume every base function below is virtual unless stated.
struct Base { virtual void f(); virtual void g() const; void h(); };
struct Derived : Base { void f() override; void g() override; void h(); };
Derived d; Base& b = d; Base* p = &d; const Base cb = d;
1. d.f(); 2. b.f(); 3. p->f();
4. d.g(); 5. b.g(); 6. cb.g();
7. d.h(); 8. b.h(); 9. Base{v}.f(); // v is a Derived
Success looks like. Nine verdicts, each with the deciding reason — virtual present
or not, handle or object, and for line 9 the word slicing.
Stuck? Concepts §1 and §2.
W2 — name the mechanism¶
Task. Match each symptom to the mechanism, and say whether a compiler message is produced:
| Symptom | Candidate mechanisms |
|---|---|
| A copy loses data that the original had | (a) missing virtual |
| The base version runs through a base reference | (b) slicing |
| A buffer owned by a derived object is never freed | (c) non-virtual destructor |
override fails to compile |
(d) signature mismatch |
| A virtual call in a constructor prints the base's text | (e) incomplete object |
A derived print(std::string) breaks print(int) call sites |
(f) name hiding |
Success looks like. Six mappings, and for each one yes/no on the diagnostic — three of them are silent, and knowing which is the point.
Stuck? Misconceptions and mistakes.
W3 — predict four virtual calls¶
Task. Without running
examples/module-07/05-virtual-calls-in-constructors/main.cpp, write the four lines
that describe() prints for one Derived object in these positions: inside Base()'s
constructor, inside Derived()'s constructor, inside ~Derived(), inside ~Base().
Then run it and compare.
Success looks like. Four lines, written before the run, plus the reason for each transition in one clause. A wrong prediction corrected in your own words is worth more than a right one with no reason.
Stuck? Concepts §7.
Core [L2 Apply] · [L3 Modify] · [L4 Analyze]¶
C1 — make the fleet bill correctly [L2 Apply]¶
Task. Take the non-virtual half of
examples/module-07/01-virtual-or-not/main.cpp, and make the fleet total correct
without changing any printed text. You will need exactly one keyword, and you should
also decide whether any other function in that hierarchy needs it.
Success looks like. The non-virtual totals become 780000 and every per-invoice
figure agrees with the virtual version's. diff the two halves of the program's output
and be able to explain any remaining difference.
Stuck? Concepts §1.
C2 — three defects, two of them silent [L3 Modify]¶
Task. This program compiles, runs, and is wrong in three ways. Find all three before you change a line.
class Account {
public:
Account(long long balance) : balance_(balance) {}
long long interest() const { return 0; } // (1)
virtual std::string kind() const { return "account"; }
private:
long long balance_;
};
class Savings : public Account {
public:
Savings(long long balance, int rate) : Account(balance), rate_(rate) {}
long long interest() const { return rate_; } // (2)
std::string kind() const override { return "savings"; }
private:
int rate_;
};
long long totalInterest(std::vector<Account> accounts) { // (3)
long long total = 0;
for (Account account : accounts) total += account.interest();
return total;
}
int main() {
std::vector<Account> accounts{Savings(1000000, 50000), Account(2000000)};
std::cout << totalInterest(accounts) << '\n';
}
Success looks like. Three named defects, each with the line that would fix it, and a predicted output before the fix with a one-sentence reason. Requirement: your fix for (3) must hold handles, and you must say what new question that introduces about lifetime.
Stuck? Misconceptions and mistakes, mistakes 1 and 5; Concepts §6 for the container rule.
C3 — the declared-type test on paper [L4 Analyze]¶
Task. Take examples/module-07/04-slicing/main.cpp and perform
the declared-type test for every call in it: static type, virtual,
handle form. Six or seven lines of output, one per call site.
Success looks like. A completed table where the row for byValue shows
handle: object and the consequence words no derived part to dispatch to — and
where you can point at the two rows that differ only in handle form and explain why
their results differ.
Stuck? Debugging.
C4 — the missing virtual [L4 Analyze]¶
Task. A colleague's hierarchy prints base (unknown) for every object in a report
loop. You have one file and two minutes. Below is what the loop sees; find the defect,
name the line, and write the diagnosis as a comment you would leave in the code.
class Report {
public:
virtual ~Report() = default;
std::string summary() const { return "base (unknown)"; }
virtual std::string title() const { return "report"; }
};
class SalesReport : public Report {
public:
std::string summary() const { return "sales: 42 invoices"; }
std::string title() const override { return "sales"; }
};
void printAll(const std::vector<Report*>& reports) {
for (const Report* report : reports) {
std::cout << report->summary() << '\n'; // prints "base (unknown)" every time
}
}
Success looks like. The word virtual on the right line, an explanation that mentions
the static type of the expression, and a note saying why title() was unaffected —
which is the half of the diagnosis that proves you understood rather than guessed.
Stuck? Debugging's declared-type test.
Challenge [L5 Design] · [L7 Refactor]¶
X1 — one notification interface, many channels [L5 Design]¶
Task. A university system must notify students by email, SMS and push notification. Design it so that adding a channel does not edit existing code, and so that a queue of notifications can be walked without slicing.
Deliver: (a) the interface, with the destructor rule applied; (b) a queue type and the
ownership decision behind it (values, pointers, or smart pointers — choose and justify);
(c) a Channel implementation that fails (SMS rejects an over-length message), with the
failure reported rather than swallowed; (d) one paragraph on what a caller must know
about the objects' lifetimes; (e) the change you would have to make to add a channel that
sends by post, and why it is or is not a one-class change.
Success looks like. The queue holds no copies of channel objects; the interface is small enough that a fourth channel implements it without stubs; and (d) names a concrete lifetime hazard rather than saying "ownership is important".
How you will be judged. The rubric — (b) and (d) carry most of the marks, because ownership is where polymorphic designs break.
X2 — replace the tag, then measure [L7 Refactor]¶
Task. Take examples/module-07/06-switch-to-dispatch/main.cpp and go one step
further: add a fourth shape to version 1 first (count the edits), then do the
conversion to version 2, then add the same shape to version 2 (count the edits). Report
both counts.
Then measure the cost you have accepted: print sizeof of every concrete class and of
the base; time a loop of one million area() calls in each version; and report whether
the difference is visible at all, and what your measurement does not tell you.
Success looks like. Two edit counts, a table of sizes, two timings, and a paragraph that says what the numbers do not show — inlining, vtable lookup, cache effects, and the fact that one implementation's timing is not a guarantee about another's.
Stuck? Concepts §3 for what the sizes mean, and §9 for why "virtual is slow" is not an argument on its own.
X3 — the memo: where not to use virtual [L8 Evaluate]¶
Task. Write a one-page design memo for a hypothetical team: "When we will not use virtual dispatch." Your reader is a competent C++ programmer who has just finished Module 7 and now wants to make everything virtual.
Cover at least: value types and why they should stay values; hot paths, and what
evidence is required before you optimise; the difference between an operation that is
expected to vary and one that merely could; and what a class with a virtual function
can no longer be (freely copied, sorted by value, held in a std::vector without
handles). End with two rules you would enforce in code review, each one greppable.
Success looks like. Four sections, each with a rule and a reason; at least one place where you recommend virtual despite the cost, argued rather than asserted; and no claim about performance that you have not measured or cited.
How you will be judged. Whether a reader who disagrees can follow the reasoning, and whether your rules could actually be applied to a diff.
Stretch (optional)¶
- S1 — "What changes if…" you add
virtualto every function in a small type and then store a million of them in astd::vector. Measuresizeofand the vector's total memory, and say what the change cost. - S2 — "What changes if…" the container in Example 4 held
std::reference_wrapper<const Person>instead ofconst Person*? Which of the three rules it breaks or preserves, and what new syntax it demands. - S3 — "What changes if…"
Base::describe()in Example 5 were non-pure and non-virtual? Predict all four lines of the trace, then run it and explain any difference from the version withvirtual.
Solutions¶
| Exercise | Reference |
|---|---|
| W1, W3, S3 | examples/module-07/05-virtual-calls-in-constructors/main.cpp — the four-answer trace, with the reason for each line |
| W2 | Misconceptions, Part 1 — the six mechanisms, with the three silent ones marked |
| C1 | The two hierarchies in examples/module-07/01-virtual-or-not/main.cpp; the correct total is 780000 |
| C2, C3 | examples/module-07/04-slicing/main.cpp — slices measured with sizeof, and a container of pointers that fixes them |
| C4 | Misconceptions, Mistake 1 — the same defect with a different surface |
| X1 | No published answer: the ownership decision is the exercise. Module 11 is the destination, but a raw-pointer answer with a stated lifetime rule is acceptable here if the rule is honest |
| X2 | examples/module-07/06-switch-to-dispatch/main.cpp — version 1 and version 2 side by side, including the -Wswitch caveat |
| X3 | Concepts §9 is the starting list, not the memo. The judgement is yours |
X1 and X3 have no published answer on purpose. Compare with the rubric after writing your own.
Next: Lab 7 — the motor pool gets an interface, and you reproduce slicing on purpose before you fix it. · All modules