Skip to content

Module 2 — Exercises

Every exercise states its level and its success criterion, so you can tell whether you succeeded without reading a solution. Write your attempt first — reading a solution produces recognition, and recognition is not the skill being tested.

The exercises build on the module's five programs in examples/module-02/. Have them open.


Warm-up [L1 Understand]

W1 — class, object, struct

Task. Answer in one sentence each:

  1. What is the difference between a class and an object?
  2. What is the only language difference between class and struct?
  3. Which of these should be a struct and which a class, and why? (a) Account with a balance that must never go negative; (b) Point with an x and a y and no rules; (c) Book with an identifier that never changes.
  4. In examples/module-02/header-source/, which file holds the declarations, and which holds the definitions?

Success looks like. Four answers, none longer than two sentences, and — for question 3 — a reason that mentions a rule rather than a preference.

Stuck? Concepts §1–2.

W2 — predict, then check

Task. Predict the exact output, and say which lines fail to compile and why.

#include <iostream>
#include <string>

class Ticket {
public:
    Ticket(int number, const std::string& event) : number_(number), event_(event) {}
    int number() const { return number_; }
    const std::string& event() const { return event_; }
    void cancel() { cancelled_ = true; }
    bool cancelled() const { return cancelled_; }
private:
    int number_;
    std::string event_;
    bool cancelled_ = false;
};

void show(const Ticket& t) {
    std::cout << t.number() << ' ' << t.event() << ' ' << t.cancelled() << '\n';
}

int main() {
    const Ticket first(1, "Orientation");
    Ticket second(2, "Orientation");
    second.cancel();
    show(first);
    show(second);
    // (a)
    first.cancel();
    // (b)
    std::cout << first.number_;
    // (c)
    std::cout << first.event().size() << '\n';
}

Success looks like. The two printed lines correct, plus a named cause for (a), (b) and (c): which is a const violation, which is a private-access violation, and which one is fine even though it looks suspicious. The third one is the point of the exercise.

Stuck? Concepts §5 and §7.

W3 — which functions must be const?

Task. Take the Book class from examples/module-02/01-book-class/main.cpp and list every member function, marking each one const or not, with a one-line reason.

Success looks like. Five members, correctly marked. If you marked correctPagesTo as const, re-read §7 — the reason matters more than the mark.

Stuck? Concepts §7.


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

C1 — write a class of your own [L2 Apply]

Task. Write a Vehicle class with:

  • a registration plate and an odometer reading in kilometres, both set at construction;
  • drive(int km) — increases the odometer, refuses a non-positive distance;
  • needsService() const — true once the odometer passes 15 000 km;
  • describe() const — prints one line;
  • no way for a caller to write to the odometer directly.

Then use it: create two vehicles, drive one of them past the service threshold, and print both.

Success looks like. Builds with no warnings under g++ -std=c++17 -Wall -Wextra; two vehicles with independent odometers; the threshold crossing at exactly the right kilometre; and a const Vehicle somewhere in your program that still compiles.

Note. Notice the design decision you are being asked to make: is needsService() a stored bool or a computed answer? One of the two can go stale. Say which you chose and why in a comment.

C2 — break it deliberately, four ways [L3 Modify]

Task. Take your C1 program and break it in four ways, predicting the message before compiling each one:

  1. read vehicle.odometer_ from main;
  2. remove const from needsService() and call it on a const Vehicle;
  3. delete the semicolon after the class's closing };
  4. declare a member function in the class and never define it, then build the program.

For (4) you must separate the declaration from the definition, so split the class into a header and a source file first — which is exercise C4 anyway, and doing them together saves a pass.

Success looks like. Eight lines: four predictions, four actual messages — and each actual matched to its prediction or the difference explained. Every one of the four messages appears, in full, in Misconceptions and mistakes; compare rather than guess.

Stuck? Debugging.

C3 — measure what a change costs [L3 Modify] (deliberately tedious)

Task. Add an owner field to:

  1. Module 1's procedural baseline, examples/module-01/01-procedural-baseline/main.cpp;
  2. Lab 2's class-based version, examples/labs/lab-02/solution/ — or your own if you have done the lab.

Success looks like. Both programs work and show the owner, plus a table:

Version Lines changed Functions changed Call sites changed
Procedural
Class-based

The table is the exercise. Do not estimate — count, using diff on a copy of each file. This is the same measurement Module 1 asked for, made again now that you have seen both shapes, and it is the evidence behind everything the course claims.

C4 — split a class across two files [L2 Apply]

Task. Move your Vehicle class into Vehicle.h and Vehicle.cpp, leaving main in main.cpp, and build it with one command:

g++ -std=c++17 -Wall -Wextra main.cpp Vehicle.cpp -o vehicles

Success looks like. It builds and runs from that command, and you can answer:

  1. What error appears if you omit Vehicle.cpp from the command, and at which stage?
  2. Why does Vehicle.h not need #include <iostream> even though describe() prints?
  3. What happens if you remove #pragma once and include Vehicle.h twice?

Stuck? Concepts §8.

C5 — who owns the rule? [L4 Analyze]

Task. examples/module-02/04-objects-interacting/main.cpp has two rules: "at most three courses per student" and "at most three students per course". Add a third: a student may not enrol in the same course twice.

Answer on paper first:

  1. Which object owns this rule — Student, Course, or both?
  2. What does each owner need to know to enforce it?
  3. Where must the check go so the two objects cannot disagree about the outcome?

Then implement it, and prove it with a scripted run that attempts a duplicate enrolment.

Success looks like. One sentence naming the owner and why; the implemented rule; and a demonstration that the duplicate adds nothing to either object.

Full credit includes noticing that if the check lives in Student and the record is added by Course, a refusal by one must prevent the other's write — the ordering question from §9 of Concepts.


Challenge [L4 Analyze] · [L5 Design]

X1 — design before you code [L5 Design]

Task. An organisation records employees. Each has a name, a staff number, and a monthly basic salary. Salaries rise by a percentage after review, and are never reduced. A record must never exist with a salary of zero or less.

Produce, on paper, before writing any code:

  1. The invariant, in one sentence.
  2. The public interface, as a table: one row per member, with the question a caller is asking.
  3. The representation, with a note on why each member is needed and what type it is.
  4. One thing you deliberately did not provide, and what a caller would do instead.

Then implement it, and build it with no warnings.

Success looks like. A class (not a struct) with no setter for the salary, a raiseBy-style operation that cannot reduce it, and — critically — a salary stored in the smallest currency unit or with an explicit comment about why a double was or was not chosen.

How you will be judged. On the exclusions and the reasons, as much as the code. An interface with nine getters and five setters has not made any decisions.

X2 — find the leaks [L4 Analyze]

Task. Read examples/labs/lab-02/solution/library.h and library.cpp. Find every place where the interface leaks something a caller should not be able to depend on.

At least three are findable:

  • something that hands out a mutable pointer to internal data;
  • something that makes a type decide how it will be presented;
  • something whose name promises less than it does.

For each: what can a caller do with it that the design should have prevented, and what is the smallest change that closes it?

Success looks like. Three specific findings, each citing a line, each with a consequence demonstrated in words — "a caller can now do X, and the catalogue's invariant Y is then false" — and a minimal fix.

Full credit includes proposing a fix that does not require changing any correct caller. This is a preview of Module 4's central test, and the reference discussion is in examples/labs/lab-04/solution/justification.md.

X3 — the one-sentence responsibility test [L5 Design]

Task. Take the four candidate types from Module 1's analysis — Book, Member, Loan, Catalogue — and write each responsibility as one sentence without the word "and".

Then:

  1. Which of them, if any, fails the test, and what would you split or merge?
  2. Catalogue both stores items and answers questions about them. Is that one responsibility or two? Argue both ways in two or three sentences, then choose.
  3. Name one thing about the system you deliberately would not make a type, and why.

Success looks like. Four one-sentence responsibilities; for (2) a position taken with a reason, not a summary of the debate; and for (3) a decision that would survive a review.

How you will be judged. This is the analysis step Lab 9 formalises. A design in which everything becomes a type has not made any decisions, and (3) is where you prove you can say no.


Stretch (optional)

  • S1. In examples/module-02/04-objects-interacting/main.cpp, make fillWithoutAsking affect the caller without changing its parameter type from Course. Then change it to take Course& and say, in two sentences, what you had to give up in the first version and what you gained in the second. Module 9 formalises the vocabulary, but the intuition is available now.
  • S2. Compile one of your classes with clang++ instead of g++. Compare the two compilers' messages for the same error from C2, and note which one you found easier to act on — you will be choosing a compiler for years.
  • S3. Add a describe() member to Course in the same example and have Student::describe() not call it. Then write a version where Student does call it, and say which is better and why. (Hint: the answer depends on what a student record is for. Neither answer is wrong; having a reason is what is being marked.)

Solutions

Reference programs live in examples/module-02/:

Exercise Reference
W2 examples/module-02/02-const-members/main.cpp — the same questions, with two of the three errors commented in place
C1, C4 examples/module-02/05-bank-account/main.cpp, and examples/module-02/header-source/ for the file split
C3 examples/labs/lab-02/solution/ for the class-based version
C5 examples/module-02/04-objects-interacting/main.cpp for the ownership pattern
X2 examples/labs/lab-04/solution/justification.md — the reference discussion, written for a later lab but about exactly these leaks

C2 has no published solution on purpose: the four messages are in 03-misconceptions-and-mistakes.md, and matching your prediction against them is the exercise.

X1 and X3 have no single right answer. Compare your reasoning with Module 4's invariant-first order and with examples/labs/lab-04/solution/justification.md.


Next: Lab 2 — the same library desk as Lab 1, rebuilt with types. · All modules