Module 4 — Self-Assessment¶
Nine questions. Closed-book, in writing, about twenty-five minutes.
Three of them ask you to look at code and find the problem — which is the module's actual skill, and the one a multiple-choice quiz cannot measure. Write your answer before reading on; the section to re-read is named beside every question.
The questions¶
- Define encapsulation in one sentence. Then explain why this class does not have it:
class Member {
public:
int borrowedCount() const { return borrowedCount_; }
void setBorrowedCount(int count) { borrowedCount_ = count; }
private:
int borrowedCount_ = 0; // the rule: 0 <= count <= 3
};
-
What is an invariant, and what are the two properties it must have to be worth writing down?
-
Which of these four public members leak the representation, and how?
std::vector<Book>& books(); // (a)
const std::vector<Book>& books() const; // (b)
std::vector<Book> books() const; // (c)
bool isOnLoan(int id) const; // (d)
- Here is a member function. What is wrong with it, and what does the compiler say if you
remove its
const?
-
Rewrite
void setDueDate(Date date)as a capability. Say what a caller can no longer do, and why that is the point. -
When is
mutablecorrect? State the test in one sentence, and give one legitimate and one illegitimate use. -
State the representation-swap test. Then say what in
examples/module-04/04-representation-swap/main.cppwould have made it fail, and why the capacity is not a leak in that program. -
Name the two costs of adding
friend class Catalogue;to a type, and give the case wherefriendis entirely ordinary. -
A function
bool lend(int id, const std::string& memberName)returnsfalse. List four distinct reasons it could have refused, and say what a caller would need in order to tell them apart. Then say which of the four you would make impossible instead of reporting.
Answers¶
1. Encapsulation is making invalid states impossible to reach: the data is private, every public operation preserves the invariant, and the interface expresses what the system does.
The class above fails the second and third parts. Its rule — 0 <= count <= 3 — is enforced
nowhere: setBorrowedCount(-2) compiles, changes the object, and produces an object whose
every answer is now meaningless. The data is hidden and the rule is public, which is the
worst of both. private stops a caller naming the member; it does not stop them breaking the
rules through what is available.
2. An invariant is a statement that must be true of every valid object at every moment a caller can observe it. Its two required properties: it is established at construction (no invalid object can exist), and it is preserved by every public operation (no operation can make it false). A rule that is documented but breakable is a wish, not an invariant.
3.
| Member | Verdict |
|---|---|
(a) std::vector<Book>& books() |
Leaks, silently. The caller receives a mutable alias to internal storage and can add, remove or reorder books behind every rule the class has. This is the most dangerous of the four and the compiler says nothing |
(b) const std::vector<Book>& books() const |
Leaks the storage type and a lifetime. No one can write through it, but the caller can keep the reference beyond the object's life (dangling), and the signature names std::vector<Book> — which is the minimum code change that makes a different representation impossible |
(c) std::vector<Book> books() const |
Leaks the storage type — the caller can depend on "it is a vector" — but not mutation or lifetime. This is the safe default, and the cost is a copy. A narrower answer is often better: std::string catalogueText() const |
(d) bool isOnLoan(int id) const |
No leak. It answers a question, returns a value, and says nothing about storage. This is the shape to aim for |
4. It hands out a mutable reference to internal state — so any caller can modify the
object's contents, and every rule the class enforces is optional. The const does not save
it: const protects the object, not what the function returns.
If you remove the const, it compiles silently, and that is worse — the same leak is now
available from a non-const object with no compiler complaint. If you leave the const in,
the compiler refuses it:
error: binding reference of type 'std::vector<std::__cxx11::basic_string<char> >&' to
'const std::vector<std::__cxx11::basic_string<char> >' discards qualifiers
The correct fixes: return by value (std::vector<std::string> titles() const), or return a
const& with a documented lifetime, or — best, if the caller only wants to display them —
offer std::string titlesText() const.
5. The capability depends on what the system means by a due date, and the honest answer involves the rule rather than the field:
bool renew(int extraDays); // extends from the current due date, refuses if lapsed
bool setDueDate(Date date); // ← still a setter, but now it has a rule to enforce
A caller can no longer put an arbitrary date in — no date before the loan began, no date in
the past, no silent acceptance of a nonsense argument. That is the point: the rule lives in
one place and every caller inherits it, rather than each caller being trusted to remember it.
The bool matters too: a refusal the caller can see beats an object silently changed.
6. mutable is correct when the member is not part of the object's observable state.
The test in one sentence: can a caller tell that it changed? If the object's answers, output
or behaviour are the same either way, mutable is describing the truth.
- Legitimate: a memoised result or cache in a
constmember function that computes something expensive once. - Illegitimate: a flag that changes what the function returns next time — which makes the
constclaim false, and is worse than a public field because the interface now lies.
7. The test: if I changed the private representation completely, would any correct caller need to change? If yes, the representation leaked.
In that program, any of these would have made the diff non-silent or broken version 2: a
public books(), begin()/end(), a signature naming std::vector, or returning a
std::vector<std::string> that the caller could mutate.
The capacity is not a leak because it is declared as part of the contract
(kMaxProducts) and enforced identically by both versions. If only the array version had
refused the ninth product, a caller could tell the two apart — and an observable behavioural
difference is part of the interface whether or not there is a signature for it.
8. The two costs: (i) the friend is now part of your implementation and may write your private members directly, so it can break the invariant; (ii) every future change to the representation must be co-ordinated with it, informally and without the compiler's help.
The ordinary case: operator<< (Module 5), and comparison operators. There is genuinely no
other way for them to reach the members, they are your own functions in your own header, and
they are naturally part of the type's public behaviour.
9. Four distinct reasons lend(id, memberName) could refuse:
- no item with that identifier exists;
- the item exists and is already on loan;
- no member with that name exists;
- the member already holds the maximum (three) items.
To tell them apart, the caller needs more than false: an operation that can only fail one
way, a bool plus a query (isOnLoan(id)), or an exception carrying the reason (Module 13).
Which one would you make impossible? The strongest answer is 1 and 3 together, by not
letting the caller express them — for example by taking a Member and a Book rather than an
int and a std::string, so that "an item that does not exist" cannot be passed in at all:
That is the module's deepest idea: the best error message is the one you made impossible to provoke.
Marking yourself¶
One mark per question. Question 3 is worth marking strictly — if you called (b) or (c) "no leak", re-read §5 of Concepts before scoring yourself, because that distinction is the one the module is built on.
| Score | What it means | What to do |
|---|---|---|
| 9 | You can design an interface and defend it | Finish Lab 4 if needed, then Module 5 |
| 7–8 | Solid judgement with one or two blind spots | Re-read the section for each miss; re-answer tomorrow |
| 5–6 | You can see leaks when shown but not yet when writing | Redo exercises C2 and C5 on code of your own, and run the attack test |
| 0–4 | The module needs a second pass; this is the most common place in the course to need one | Work through Worked examples again, typing the programs, and do the Example 5 table for three of your own classes |
Which section to re-read, by question:
| Missed | Re-read |
|---|---|
| 1 | Concepts §1–2, then M1 and M2 |
| 2 | Concepts §2 |
| 3 | Concepts §5 — read all four kinds |
| 4 | Misconceptions D2 and D4 |
| 5, 9 | Concepts §4 and §10 |
| 6 | M5 in Misconceptions |
| 7 | Concepts §7 |
| 8 | Concepts §8 |
Re-test schedule¶
| When | Do what |
|---|---|
| Tomorrow | Re-answer only the questions you missed, in writing |
| In a week | Question 3 again, on a different set of six public members — from a real codebase, or a tutorial online. Classify each one |
| In a month | Run the attack test on a class in whatever module you are on, and count how long it takes you to find a leak. Under five minutes means this module is finished for good |
Ready to move on when¶
- [ ] You scored 7 or more, or re-answered the misses correctly
- [ ] Lab 4 builds with no warnings, and its
justification.mdhas a row per public member - [ ] Your
justification.mdlists the members you deleted, and the list is not empty - [ ] You can look at a class and say what a caller cannot do to it
- [ ] You have run the attack test on your own code and found something
- [ ] You can state any type's invariant in one sentence, or say deliberately that it has none
Next: Summary — what must be retained, and the checklist. · All modules