Challenges — 10 problems
← Module home · ★ to ★★★★. Each has a separated approach — design and commit before reading. Build the ★★ and above; every challenge ends with a defence sentence: the one sentence you’d say if asked “why this relationship?”
- C1 ★ — The animal chorus. Abstract
Animal(purespeak() const, virtual destructor, sharedname), three implementers, and a chorus loop overconst Animal* const arr[], int n. Print each speaker. Defence sentence required. - C2 ★ — The shape statistics. Abstract
Shape(purearea(), pureperimeter(), shared non-virtualdescribe()calling both) with Circle, Rect, Triangle. Astats(const vector<Shape*>&)free function reports total area, max perimeter, and the kind with the largest area — without anydynamic_castor type field. - C3 ★★ — The template-method pipeline. Build the report framework: abstract
Reportwith a non-virtualgenerate()that calls virtualfetchData(),formatRow(const Item&),header(),footer()in fixed order. Two subclasses (MarksReport, SalesReport) implement only the hooks. Verify: adding a third report touches zero framework code. - C4 ★★ — The interface audit. Given a fat interface (below), split it so no implementer stubs anything, keep
workshop(...)loops compiling for each capability, and write the two-sentence defence.class Machine { public: virtual void drill() = 0; virtual void paint() = 0; virtual void weld() = 0; virtual void polish() = 0; virtual ~Machine() = default; };with DrillPress (only drills), Painter (only paints), Welder (only welds), Polisher (only polishes). - C5 ★★ — The cloning question. Abstract
Tilewith pureclone() const -> Tile*(each subclass copies itself) and aTileBoardthat owns a grid ofTile*and can duplicate itself via clone. Why can’t the board just copy the pointers? What does clone cost each new subclass? - C6 ★★ — The composed versus the inherited. Build the same feature twice: a
Loggerused byBankAccount— version A composes (BankAccount has-a Logger&), version B inherits (LoggingBankAccount : BankAccountadds logging to every method). Write both, then answer: which version can log aSavingsAccountwritten later, by someone else? Which version can swap loggers at runtime? Verdict sentence. - C7 ★★★ — The registry. A
ShapeFactoryholds a map from a name (“circle”) to a maker function (Shape* (*)()), withregisterKind(name, maker)andcreate(name) -> Shape*(nullptr when unknown). Register Circle and Rect; create through the registry; loop-delete. What never changes when Triangle arrives — and what must change? - C8 ★★★ — The double-dispatch taste. A collision system:
Shape::collide(const Shape& other) constmust print which two concrete kinds met. With single virtual dispatch, insideCircle::collide(other)you know “I am a Circle” but not whatotheris. Sketch the two-branch solution (other.asCircle()/asRect()probing) and honestly state why this is the classic awkward case — and what real systems use instead (name it; you don’t build it). - C9 ★★★★ — The plugin host. Combine C7’s registry with Lesson 3’s plugin contract:
PluginHostownsvector<PlayerPlugin*>registered by third parties,playAll(stream)tries each plugin in registration order, and the host deletes all plugins at shutdown. Write the ownership contract in comments at the class head, and answer: what does a plugin author guarantee, and what does the host guarantee? - C10 ★★★★ — The refactor defence. Here is a legacy class:
class Order { public: double total(bool member, bool sale, int region, bool giftWrap) const; };— a four-flag method with a 16-branch body. Refactor to polymorphism or composition (choose, defend), withPricingbehind an interface, region as data, and gift-wrap as a decorator-style add-on. Present the before/after call sites, and state what the 17th pricing rule costs in each design.
Approaches and solutions
A1. class Animal { public: Animal(const string& n) : name(n) {} virtual void speak() const = 0; const string& getName() const { return name; } virtual ~Animal() = default; private: string name; }; — implementers override speak (+override). Chorus: for (int i = 0; i < n; i++) cout << arr[i]->getName() << " says: "; arr[i]->speak();. Defence: Animal is abstract because no honest animal has no sound; every listed species refines the one contract, so the chorus works unchanged for any future animal.
A2. stats holds const Shape* best = nullptr; double bestArea = -1; and accumulates — all through the interface; “which kind” is answered by which pointer won, no type probing needed (if the report must name kinds, give Shape a pure name() const — data about the object through the contract, not a cast around it). Defence: the function reads rules through the interface; kinds are data, never branches.
A3. void generate(ostream& out) const { header(out); for (each item) formatRow(item, out); footer(out); } — non-virtual skeleton, four pure (or defaulted) hooks. MarksReport implements fetch/format; SalesReport likewise; the third report = one new subclass. Defence: the pipeline’s order is policy (fixed once) and the content is mechanism (per report) — the template-method split. (This is Lesson 2’s describe() pattern promoted to a framework.)
A4. Split by capability: Driller { virtual void drill() = 0; }, Painter { ... }, Welder { ... }, Polisher { ... } — each with a virtual destructor; DrillPress implements Driller only, and so on. workshop(vector<Driller*>&) drills every driller; the other loops likewise. Defence: no machine is asked to do work it doesn’t do; capability interfaces are admission tickets, and stubs become impossible. (Fat-base gallery #2, cured.)
A5. The board can’t copy pointers because two boards would then share tiles — deleting one board or clearing one grid destroys the other’s objects (double-delete on death, mutations visible across boards). clone() makes each subclass responsible for producing a fresh, correct copy of itself — the only place that knows its own fields. Cost per new subclass: one override (Tile* clone() const override { return new Kind(*this); }). Defence: ownership demands deep copies, and only the concrete class knows its depth. (The pattern’s proper name — the prototype/clone idiom — is worth writing down; smart-pointer copy machinery is next course.)
A6. Version A: class BankAccount { public: explicit BankAccount(Logger& log) : log(log) {} protected: void logEvent(const string& e) const { log.write(e); } ... }; — wait: the composition version needs no protected at all: private: Logger* log; and deposit() calls log->write(...). Version B: class LoggingBankAccount : public BankAccount overrides deposit… but deposit isn’t virtual (and shouldn’t be) — so version B must re-write each method and call the base version, doubling the interface. The answers: version A can log a future SavingsAccount (logging is inside the base behaviour, inherited automatically); version A can swap loggers at runtime (re-point the reference/pointer); version B can do neither without new subclasses per combination. Verdict sentence: logging is a service the account uses, not a species the account is — composition, twice over. (C6 exists because “inherit to add behaviour to every method” is the most tempting bad trade in the gallery.)
A7. map<string, Shape* (*)()> makers; — registerKind("circle", []() { return new Circle(); });-style registration (function pointers are enough; C7’s brief allows them — challenge C15 of the Algorithms module introduced the type). create looks up, returns makers.at(name)() or nullptr. When Triangle arrives: register one more name — nothing in the factory, the loop, or the callers changes; what must change: Triangle exists and someone registers it (registration is the one honest touchpoint). Defence: the registry is the extension point — closed code, open table.
A8. The sketch: Circle::collide handles the Circle-vs-Circle case directly, then needs to distinguish other — the honest beginner tool is a downcast probe (dynamic_cast<const Rect*>(&other)), which this course names but doesn’t adopt; the conceptual answer: single dispatch chooses on the left object only, so the right object’s kind requires a second decision — the double dispatch problem; real systems use the visitor pattern (a visit(Circle&)/visit(Rect&) double-table) or a kind enum inside the hierarchy boundary. The challenge’s real payload is the honest statement: polymorphism dispatches on one object per call; interactions between two unknowns need machinery beyond Lesson 2 — named, sketched, deferred. (Gallery #8’s mirror: knowing when NOT to fake it.)
A9. The class head comments: “Ownership: the host takes ownership of every registered plugin and deletes them all in its destructor (in reverse registration order). Plugins must not delete themselves and must outlive no call they didn’t survive.” Host guarantees: registration acceptance, ordered playAll attempts, deletion at shutdown (virtual destructor doing D4’s job). Plugin guarantees: the three contract clauses (DP9) plus statelessness across failed plays (a failed play leaves the plugin usable). Defence: the host is the owner and the contract is the border — third-party code is exactly the case where trust must be structural, not personal.
A10. The choice: composition (C8’s verdict logic — pricing rules swap at runtime and multiply quarterly). After: class Order { private: const Pricing* pricing; Region region; bool giftWrap; public: long long total() const { long long t = pricing->total(items, region); return giftWrap ? t + GIFT_FEE : t; } }; with Pricing implementers (MemberPricing, SalePricing…) — region as a parameter/data, not a subclass; gift-wrap as the add-on computed after (the decorator idea in one if, honestly small). Call sites: order.total() — no flags. The 17th rule: before — edit the 16-branch body (and re-test all 16); after — one new Pricing implementer + one registration. Defence: flags were kinds wearing booleans; the polymorphic pricing gives each kind its own class and the compiler its old job back. (Gallery #10 — the switch refactor — at its most realistic.)
Where next
- The Media Library mini-project: build and defend a polymorphic system end to end.
— Engr. Dr. Muhammad Nadeem Majeed, Professor, Department of Data Science (PUCIT), University of the Punjab, Lahore · About the instructor