Module 3 — Challenge Problems¶
Open-ended on purpose. Several have no code at all, and the deliverable is a decision, defended. This module's challenges are about lifetime as a design tool: choosing when things begin, when they end, and who is responsible for the ending.
Do them on paper first. A lifetime design argued in prose takes ten minutes; the same design discovered by debugging takes an evening, and you will not know which of the three possible explanations was right.
X1 — a value type and an entity [L5 Design]¶
Two kinds of thing exist in almost every system, and they are easy to confuse:
- a value: a card number, a date, an amount. Copy it freely, compare it, store it, print it. Two values with the same content are interchangeable, and neither "owns" anything.
- an entity: a member, a book, a loan. It has an identity that persists while its attributes change. Copying it is a decision, not a default.
Deliverable, in writing, then in code:
- Design
CardNumberas a value type for the library system: a prefix and a serial, settled at construction, never changed. State its invariant in one sentence. - Design
Memberas an entity: a name, a card number, and a set of items currently held — where the card number itself never changes. - For each type, answer: is it copied by value, held by reference, or not copied at all? Justify each answer with what happens on the trace — how many constructions, copies and destructions a typical operation produces.
- Name the boundary. What made you decide that one of these is a value and the other an entity? Two sentences, and "it has an id" is not an answer on its own.
Success looks like. Two types that both compile, with const members where the design
says immutable, and a trace-based justification of each copy decision. In CardNumber, a
copy should be cheap and expected; in Member, a copy should be something you can point
at and explain.
How you will be judged. On (4). The distinction is not about size or about keywords — it is about whether two of them can be the same thing, and the argument is what is being marked.
X2 — count every object in a program you wrote [L4 Analyze]¶
Take a program of your own from Module 2 — a Vehicle, a BankAccount, a Lab 2 type —
and count, without running it:
- every construction;
- every copy (by-value parameters, copy initialisation, returning a named local, inserting into a container);
- every destruction;
- for each copy, the exact line that causes it.
Write the count as a table annotated with line numbers, then instrument the program and prove yourself right or wrong.
| # | Line | Event | Why |
|---|---|---|---|
| 1 | main L12 |
construction | BankAccount account("Ayesha", 250000); |
| 2 | L20 | copy | passing to show(const BankAccount&)? — no: a const& parameter does not copy |
| … |
Success looks like. A table covering every event, and a list of the ones you got wrong — with an explanation of the mechanism for each, not a correction.
Full credit includes at least one event you predicted that does not happen (a reference parameter is the classic), and one that happens that you did not predict.
How you will be judged. On the list of errors. A perfect prediction is possible but less informative than an honest list of three mistakes with the reasons.
X3 — where should a rule be enforced: construction, operation, or caller? [L5 Design]¶
The library system has rules. Four of them are listed below with a choice of enforcement point. For each, decide and defend: established by construction, checked by an operation, or required of the caller.
- An item's identifier is positive and never changes.
- A member holds at most three items at once.
- The title of a book is at most 120 characters (the label printer's limit).
- A loan's due date is fourteen days after the loan starts.
For each of the four:
- where you would enforce it, with one sentence of justification;
- what the failure looks like at that enforcement point — an exception, a
false, aboolthe caller checks, or nothing at all because it cannot fail; - what the code that calls it must do. If the answer is "nothing", say so; if it is "handle it", say how.
Then pick the one you are least sure about, and write the argument against your own answer in three sentences.
Success looks like. Four decisions with named failure modes, and a genuine counter-argument for one of them.
How you will be judged. On (1)-versus-(2)-versus-(4): a rule enforced by construction cannot fail, one enforced by an operation can be refused, and one required of the caller will be forgotten. Choosing the strongest available point — and recognising where the strongest point is not available — is the skill.
X4 — the lifetime budget for one operation [L5 Design]¶
Here is a plausible interface for the lending operation:
Deliverable:
- Who owns the returned
Loan? Who destroys it, and when? Write the answer in two sentences as if explaining it to the caller of this function. - Why
const&on both parameters rather than by value or non-const reference? Give one reason per choice, and one case where each of the alternatives would be correct. - What is wrong with returning by value if the system goes on to store the result in a
std::vector<Loan>? (Module 10 has the full answer; give the shape of it now.) - Redesign the signature so that the lifetime story is unambiguous — and say what you changed and what you gave up.
Success looks like. Four answers that agree with each other, and a redesigned signature whose ownership question answers itself.
Full credit includes noticing that a function which creates a loan may not be the right place to store it, and that the two decisions can be separated. That separation is Module 9's subject and Module 15's pattern.
Stretch (optional)¶
- S1. Write a class whose constructor always throws. Instantiate it in a scope after another object has been fully constructed, and observe which destructors run. Then write one paragraph on what this tells you about partially constructed containers. (Module 13 formalises it; the observation is available now.)
- S2. Add a
static"objects ever constructed" counter to a class and a separatelivecounter. Print both at the end of the program. Explain, in terms ofstaticstorage duration, why the ever constructed counter cannot be a member.
Evaluating your own answer¶
Use this on every design above, and on your own lab work.
| Criterion | A strong answer | A weak answer |
|---|---|---|
| Initialisation | Every constructor produces a valid object by initialising members in the list; the body does additional work only | Members assigned in the body, with the compiler errors suppressed by removing const |
| Construction order | You can state, for any object, the order its members are initialised in, and it matches the declarations | "It works, so the order must be fine" — the compiler warned and the warning is treated as an error here for a reason |
| Immutability | const members are used where the design says the value never changes, and you can name the cost (no assignment) |
const added because it seemed safer, without noticing what it takes away |
| Ownership | You can say who destroys every object, and when, in one sentence per object | "C++ cleans up after itself" — true only for objects whose lifetime the language controls, which is exactly the question |
| Copies | Every copy is accounted for, with the line that causes it and a reason it is acceptable | "There are some copies somewhere" — a later performance or correctness bug waiting to happen |
| Destructors | Written only where something is owned, and paired with copy constructor and copy assignment when they are | Written for every class, or omitted for the one class that needs it |
| Failure | Constructors that cannot establish the invariant refuse — and the refusal is not silent | A constructor that "fixes" a bad argument into a default value without telling the caller |
Diagnostic. If Initialisation and Construction order are weak, that is a syntax problem and the compiler will help you — re-read §4 of Concepts and rerun the exercises. If Ownership and Copies are weak, that is conceptual: the module's programs are the cure, and every one of them is a trace you can predict. If Failure is weak, you are about to meet Module 4, where it becomes the whole subject.
Next: Self-assessment — nine questions, with answers and a re-test schedule. · All modules