Module 4 — Lab 4: seal the system¶
Levels: [L2 Apply → L5 Design] · Extends: Lab 3 · Time: 4–5 hours
The scenario¶
The library system from Lab 3 works, and its object lifetimes are understood. Now the library has a review process, and the system has to pass it.
Two things have just been discovered in production:
- a member was found holding minus one item. Nobody knows how it happened. The item count was on a screen, and someone "corrected" it.
- a book was reported as both available and on loan at the same time, by two different screens. Both were reading the same object.
The review board asks one question: for each of your types, what exactly can a caller do to it? This lab answers that — by making the answer short, and by justifying every line of it in writing.
★ The written justification is half the lab. This is the first lab whose deliverable is as much a document as a program, and it is the shape of every design review you will ever take part in: here is my interface, here is why each member is public, here is what I deliberately did not provide.
What you start from¶
Your Lab 3 program: Book, Member and Catalogue in a header and a source file, with the
limit of three enforced by Member, and the lifecycle instrumentation removed.
The command set is unchanged:
| Command | Meaning |
|---|---|
A <id> <title> |
add an item |
M <name> |
add a member |
L <id> <member> |
lend item <id> to <member> |
R <id> |
take it back |
P |
print items and members |
Q |
quit |
Requirements¶
- State the invariant of every type, in one sentence each, as a comment immediately above the class — where a reader meets it first. A type with no invariant should say so explicitly rather than by omission.
- No public member that merely reads or writes a field. The test: for every public member, write the caller's sentence. "This needs to know … so that it can …" If the sentence ends nowhere, delete the member.
- Every operation that can fail refuses totally — it returns
falseand leaves every object involved exactly as it was.lendis the interesting one: it touches aBook, aMemberand the catalogue. - The deliberate failure-conflation from Lab 3 is resolved. If a caller can now tell
which failure occurred — by a
boolplus a query, or by an operation that can only fail one way — say how in the justification. (Throwing is Module 13's answer; aboolplus a query is fine here.) - No leaks of the representation, checked against the four kinds in
Concepts §5:
no returned mutable reference, no iterator or pointer into storage, no signature naming a
container or capacity as a storage detail, no
const&return whose lifetime the caller can outlive. const-correctness. Every observing member function isconst, and a free function takingconst Catalogue&can print the whole system.- A validation rule that did not exist before: a title may not be empty, and an identifier must be positive. Both are established at construction and checked by the operations that accept one.
- The attack test passes. Write the attacks from Debugging as a file, keep it, and comment it with what each attack is for.
- A written justification —
justification.md— with one row per public member of every type. - No warnings, and the Lab 3 command session still produces exactly the behaviour you expect (the counts will differ, because the reply text is now more precise — say so in the justification rather than leaving a silent change).
Requirement 10 is subtler than it looks
Sealing a system changes what its operations return, and therefore what it prints. The honest thing to do is to write down what changed and why, in one short section. A lab where outputs changed "somehow" is a lab that has not been finished.
What to deliver¶
- The sealed program, building cleanly.
justification.md, with:- each type's invariant, in one sentence;
- a table of public members: the promise it makes, and why it is public;
- a section listing the members you deleted and what a caller does instead — this is the part reviewers actually read;
- one member you are unsure about, with the argument on both sides;
- a short note on what changed in the program's output and why.
attacks.cpp(or an equivalent section of your notes): the attacks you tried and the lines that now fail to compile, with a one-line comment each.- The two production bugs, explained: for each, the line of your old code that allowed it, and the line of the new code that makes it impossible.
Self-assessment checklist¶
The invariant is real
- [ ] Every type has its invariant stated above the class, in one sentence
- [ ] Each invariant names the type that owns every rule in it — no rule is shared
- [ ] Every operation that changes the object either preserves the invariant or refuses
The interface is honest
- [ ] Every public member has a caller's sentence in
justification.md - [ ] No public member returns a mutable reference, iterator or pointer into storage
- [ ] No signature names a container, a size type or a capacity as a storage detail
- [ ] The deleted-members section is longer than you expected
Refusals are total
- [ ] A refused
lendleaves theBook, theMemberand the catalogue unchanged - [ ] A refused
giveBackcannot make a member hold fewer items than it has - [ ] The fourth item for one member is refused while another member can still borrow
- [ ] Each refusal's reason is available to the caller, or its uniqueness is explained
It is checked, not asserted
- [ ] The attack file exists, its attacks are commented, and each either fails to compile or
returns
false - [ ] A
const Catalogue&can be printed in full - [ ] Empty titles and non-positive identifiers are refused at construction and from the operations that accept one
- [ ] The build has no warnings
It is explained
- [ ]
justification.mdnames the least certain member and argues both sides - [ ] Both production bugs are traced to a line in the old code and a line in the new
- [ ] The output changes are documented rather than silently accepted
Common ways this lab goes wrong¶
| Symptom | Cause | Fix |
|---|---|---|
| The justification table has a row per field | The interface is still a public representation with function calls in front of it | Rewrite the rows as operations; delete every row whose caller's sentence ends nowhere |
Catalogue::find still returns Book* |
Lab 2 left it there and it "worked" | That is the leak. Return a const Book*, or better, an operation: bool isOnLoan(int id) const, std::string describeItem(int id) const |
lend refuses but the member's count was already incremented |
The operation checks, then mutates, then finds a second problem | Check everything first, then mutate. Nothing may be written before all the checks have passed |
Everything is const except one function, and it blocks printing |
That function both observes and modifies | Split it, or accept a non-const reference in the printing function if it genuinely modifies — and say which in the justification |
justification.md is a list of descriptions ("returns the title") |
Descriptions are not justifications | Each row needs a promise and a reason: what a caller may rely on, and why they need it |
| Attacks "cannot be written" because the members are private | The attack test is aimed at the interface | Attack through the public members: borrow(""), borrow fifty times, giveBack something never held, lend an item that is already out |
| The program's output changed and nobody noticed | Requirement 10 | diff the new session output against Lab 3's and document every difference |
Reference solution¶
examples/labs/lab-04/solution/ — main.cpp and justification.md.
Read them after your own version runs, and look for three specific things:
- What the reference does not have. No setters, no getters that return storage, no way to
set a count. The absences are the design, and the
justification.mdsection on deleted members is the argument for them. - The
Booktype'sdescribe()is flagged as arguable in the reference justification. A type that writes tostd::couthas decided how it is presented, which is the caller's business — the reference keeps it for now and says why, and Module 16 replaces it. Your justification may reasonably disagree. - The comment showing the four attacks that used to compile (
setOnLoan,setBorrowedCountwith-4and withkLimit + 10, andtitlesHeld().push_back(...)). Those four lines are the before-and-after of this lab, and the reason the count is now derived rather than stored.
The reference solution's own Catalogue is not in this file — the lab's program is your
Lab 3 program with these types. If your solution keeps a Catalogue::find returning a mutable
pointer, that is the remaining leak, and finding it is
Challenge X2.
Next: Challenge problems — interface design from caller sentences, and the immutability trade. · All modules