Skip to content

Module 16 — Exercises

Every exercise states its cognitive level, its task, and the observable result that counts as success. Compile with the course's flags, treating a warning as a failure:

g++ -std=c++17 -Wall -Wextra -Werror main.cpp -o exercise && ./exercise

Multi-file exercises are built from an explicit file list — there is no build system in this course:

g++ -std=c++17 -Wall -Wextra -Werror catalogue.cpp record_format.cpp main.cpp -o app

Two habits for this module: prove it with a round trip, and count the files an extension touches.


Warm-up [L1 Understand → L2 Apply]

W1 — Split a program into three files

Task. Take one of your earlier single-file programs of at least three classes and organise it as: one public header, one internal header, and source files that implement them, plus a main.cpp that includes only the public header.

Success looks like. It builds from a documented command. The public header compiles when placed alone in a one-line .cpp file — which is the only convincing test that it has no internal dependency. And main.cpp contains no include you cannot justify in one sentence.

Stuck? Section 1 and 2 of Concepts, and examples/module-16/01-multi-file-layout/.


W2 — Say where six declarations belong

Task. For each of these, say whether it may go in a header, and if so what is required:

a. a class definition; b. a function definition; c. an inline function definition; d. a global variable; e. a constexpr variable; f. a function used only inside one source file.

Success looks like. Six answers, each citing the One Definition Rule: what may be defined in more than one translation unit, and what the standard requires of it. A constexpr variable at namespace scope is implicitly const and therefore internal linkage — say that, because it is the answer most students miss.

Stuck? Section 3 of Concepts and examples/module-16/02-one-definition-fixed-twice/.


W3 — Move two helpers into an unnamed namespace

Task. Find two functions in your capstone that are used by one source file only, move them into an unnamed namespace, remove their declarations from any header, and confirm the build still links.

Success looks like. A successful build, plus a demonstrated failure: a commented-out call from another file that does not compile, showing the names are unreachable. A claim that they are private, with no attempt to reach them from outside, has not been checked.

Stuck? Section 4 of Concepts.


W4 — Write and read back a record file, checking state at every step

Task. Save five records to a file and load them back. Check the stream at every step: after opening, after closing, after each read that could fail.

Success looks like. A successful round trip, and a second run against a deliberately missing file that produces a message rather than silence — the difference between loaded 0 records and refusing to continue: the file could not be opened. Both runs' output pasted in your notes.

Stuck? Sections 5, 6 and 8 of Concepts and examples/module-16/03-stream-state/.


Core [L3 Modify → L4 Analyze]

C1 — "Fix this": a duplicate symbol, two ways

Task. This header is included by two source files. Fix the resulting link error twice — once by moving the definition to a single source file, once with inline — and say when each is the right choice.

// totals.h
int total_items(const std::vector<Box>& boxes) {
    int sum = 0;
    for (const Box& box : boxes) { sum += box.items; }
    return sum;
}

Success looks like. Both builds link. The link message on this toolchain is nothing but collect2.exe: error: ld returned 1 exit status, so include the nm -C output that identifies the duplicate T symbol — the diagnosis, not just the fix. And a sentence per fix saying when it applies.

Stuck? Section 3 of Concepts, and Move 1 of Debugging.


C2 — Make a loader reject a hand-edited file precisely

Task. A loader mishandles a blank line and a record with a missing field: it either miscounts or misreads. Make it reject the malformed record with a line number, accept the blank line, and report the file's declared record count when it disagrees with what was found.

Success looks like. Three messages, each naming the line and what was wrong; good input still loads; and a rejection leaves the previously loaded data untouched — demonstrated by loading a good file, then a corrupt one, and printing the record count after each.

Stuck? Section 7 of Concepts and examples/module-16/05-untrusted-files/, which runs twelve corruptions through the same checks.


C3 — A program that loads a truncated file and keeps partial data

Task. This code is in a program of yours (or in the snippet below). Find the missing validation, explain what the program does with a truncated file, and fix it.

Catalogue load(const std::string& path) {
    std::ifstream in(path);
    Catalogue result;
    std::string line;
    in >> std::ws;
    while (std::getline(in, line)) {
        result.add(parse(line));     // parse() returns a default Book on failure
    }
    return result;
}

Success looks like. The behaviour reproduced on a truncated file — the program silently contains a default-constructed record — then the missing validation named (the parse result is never checked), then a fix that rejects rather than substitutes. Plus one sentence on why returning a default on failure is worse than throwing.

Stuck? Section 7 of Concepts and mistake M5 of Misconceptions.


C4 — Which of these tests would catch the bug?

Task. Three tests are given for a cap that is wrong by one day: one prints the computed fine, one asserts it at 10 days, one asserts it at 15 and 400 days. Say which would still pass if the bug were introduced, and turn the weakest into a real test.

Success looks like. Three assessments, each with the reason: the printing test passes because it checks nothing; the 10-day assertion passes because the two implementations agree there; the boundary assertions fail, and that is why boundaries are the tests worth writing. Then the rewritten test, demonstrated failing against the buggy version.

Stuck? Section 9 and 10 of Concepts and examples/module-16/06-tests-that-assert/.


Challenge [L6 Build → L8 Evaluate]

X1 — Refactor under test, with the tests unmodified

Task. Take a working source file that holds two responsibilities, split it, and keep the tests green without editing them.

Success looks like. Identical behaviour, proved by the suite passing unchanged and by the program's output being compared before and after. The split follows a responsibility boundary you can name in one sentence per file. If a test had to be edited, say what it was asserting and whether it was asserting an implementation detail — then decide whether the refactoring was worth it.

Stuck? Section 11 of Concepts.


X2 — Extend the finished system, and count the files

Task. Add a new kind of item to your completed application, and report every file you had to touch — then say whether that number matches what the design promised.

Success looks like. A working extension; an honest file list including the ones you had forgotten existed; a number; and a judgement. One or two files means the abstractions earned their place. Ten means the design promised more than it delivered, and saying so is worth more than a clean-looking answer. Either way: what would have made the number smaller?

Stuck? Section 14 of Concepts and Module 15's change-cost trace (Module 15 — Debugging, Move 1).


X3 — Write the design report for your own system

Task. Write the report: the decisions, the alternatives rejected, the costs accepted, and one decision you would reverse, with the reason you did not change it.

Success looks like. A report whose every claim rests on evidence you produced — the change-cost counts, the corruption log, the round trip, the file list from X2 — and which names a real weakness and a real reason. No excuses, no vague intentions: every entry in the form context, choice, cost, rejected option, condition that would reverse it, and the reversal's cost included.

Stuck? Sections 12 and 13 of Concepts and the rubric in Projects, which is the same rubric a marker would apply.


How to check yourself

  • [ ] Every exercise builds with -Wall -Wextra -Werror, with no warnings
  • [ ] Every claim about persistence is a round trip on real files, not a description
  • [ ] Every loader message names the line and what was wrong with it
  • [ ] Every test failure was reproduced by introducing the bug deliberately
  • [ ] Every refactoring is demonstrated by comparing output before and after, with tests unmodified
  • [ ] Every design claim is either a number or a file list, not an adjective
  • [ ] One decision in the design report is one I would reverse, with its cost of reversal named

Continue

Previous: Debugging · Next: Lab 16 — the capstone · Module home: Module 16