Module 13 — Self-Assessment¶
Nine questions. Part A checks vocabulary; Part B checks that you can predict — which, in a module about the paths that do not normally run, is what separates reading from knowing. Answer on paper, then mark yourself and note which section of Concepts each miss points back to.
Target: ≥ 80%, with every miss explained. Below 60%, redo the unwinding trace (section 4) and Lab 13 in full.
Part A — Recall and comprehension¶
-
Why can an exception not be ignored by accident, when a returned error code can? (Section 1. The returned value may be discarded —
[[nodiscard]]asks for a warning, and that is all it can do — while an exception either reaches a handler or ends the program.) -
What does stack unwinding destroy, and what does it not do? (Section 4. It destroys every fully constructed local object in the frames being left, in reverse order of construction. It does not run a
catchin a frame that was skipped, does not revisit statement-level cleanup, and does not destroy an object whose constructor never completed.) -
Why must exceptions be caught by reference? (Section 5. Catching by value copies the exception and slices it: a handler for a base type receives a base-type copy, so the derived type's accessors and classification are gone. It is diagnosed as
-Wcatch-valuewhen the type is polymorphic — a warning, not an error.) -
State the three exception-safety guarantees, and say which one costs something. (Section 8. Nothrow: the operation cannot fail. Strong: on failure, the object is exactly as it was. Basic: no leaks and the object is still valid, but its contents may have changed. The strong guarantee usually costs one copy of the state.)
-
What does
noexceptpromise, and where is that promise enforced? (Section 9. That the function does not throw. If it does, the program callsstd::terminate— without unwinding — because the caller was entitled to arrange things on the assumption that the call cannot fail. It is also enforced through inheritance: an override cannot be looser than the base —error: looser exception specification on overriding virtual function.) -
When a program dies during unwinding, why does the message often name the wrong failure? (Section 9 and Debugging, Move 1. Because a destructor threw while another exception was in flight: two failures cannot both be reported, so the standard terminates the program and only the destructor's exception is described. The original failure is lost, and the
catchthat would have logged it never runs.)
Part B — Prediction and explanation¶
- Predict the exact output, and the order. No compiler, no run.
#include <iostream>
#include <stdexcept>
#include <string>
struct Guard {
std::string name;
explicit Guard(std::string n) : name(std::move(n)) { std::cerr << "+ " << name << '\n'; }
~Guard() { std::cerr << "- " << name << '\n'; }
};
void work() {
Guard a("a");
Guard b("b");
throw std::runtime_error("failed");
}
int main() {
try {
work();
} catch (const std::exception& error) {
std::cerr << "caught: " << error.what() << '\n';
}
}
Answer.
Two constructions, two destructions, in reverse order — and the handler runs afterwards, in the frame
that asked for the operation. If you predicted the destruction order forwards, or expected the
catch to run before the destructors, re-read Concepts section 4: locals are
destroyed on the way out, before the handler gets control.
- A catalogue file has one malformed line. Predict what happens in each version, then say which you would keep.
// A
void loadFrom(const std::string& path) {
std::ifstream in(path);
std::string line;
while (std::getline(in, line)) {
items_.push_back(parse(line)); // parse throws on a bad line
}
}
// B
void loadFrom(const std::string& path) {
std::vector<Item> candidate;
std::ifstream in(path);
std::string line;
while (std::getline(in, line)) {
candidate.push_back(parse(line)); // parse throws on a bad line
}
items_.swap(candidate); // the only line that touches the catalogue
}
Answer. Both report the failure correctly, and they differ in what the caller can conclude:
- A is basic: every item up to the bad line has already been added. The catalogue is valid and nothing leaked, but its contents changed, so any caller that cached the size or is part-way through iterating it is wrong — and it has no way to know.
- B is strong: the work happens in a local vector, and
swapisnoexcept, so a failure leaves the catalogue exactly as it was. The caller may retry with a fixed file, or continue with the old contents.
Keep B, and note what it costs and what it uses: one extra container, and Module 10's
copy-and-swap idiom doing duty as a guarantee. The swap is the load-bearing line — it cannot fail,
which is what makes the last step of the commit infallible.
If you answered "A is fine because nothing leaked", that is true and insufficient: the question is not whether the program is broken, it is what a caller may assume after a failure it could not prevent.
- Give one case where returning a status is better than throwing — with a concrete reason.
Answer. Any expected, frequent, non-exceptional outcome whose handling belongs at the call site. The canonical case is a lookup in a loop:
A missing id is the normal answer to "is this here?", it happens constantly, and no caller is going to
be surprised by it. Throwing would make this loop control flow: the try would dominate the code, the
"one of these is missing" case would be indistinguishable from a genuine failure occurring inside
process, and the exceptional path would be entered thousands of times in a run. find returns;
require — the version that promises the item is there, and whose caller genuinely cannot continue —
throws. Both are in the same class, and naming them differently is what makes the choice visible.
Other good answers: a callback whose failure is its caller's business (Module 15's observer); a
noexcept boundary that must report a failure without leaving the function; a comparison operator.
Any one of these, with its reason, earns full credit. "Because exceptions are slow" does not: the
cost is on the throwing path, not on the successful one.
Marking yourself¶
| Score | What it means |
|---|---|
| 9–8 | Move to Module 14. You can explain a failure path, not merely handle one |
| 7 | Re-read sections 5 and 8, then do exercises C1 and C3 |
| 6 or below | Redo the unwinding trace by hand, then Lab 13 — including the second counter run |
For any question you missed, write the one sentence that would have led you to the right answer. That sentence is worth more than the correction itself: this module's mistakes are all plausible ones, and the sentence is what makes them implausible next time.
Continue¶
Previous: Challenge problems · Next: Summary · Module home: Module 13