Module 11 — Self-Assessment¶
Nine questions. Part A checks vocabulary; Part B checks prediction, which is the only part that tells you whether you can use this material. Answer on paper, then mark yourself honestly and note which section of Concepts each miss points back to.
Part A — Recall and comprehension¶
-
State RAII in one sentence without naming memory. Then name three resources other than memory it applies to. (See section 1 and section 2.)
-
Why does RAII matter more once exceptions exist? What exactly does the language guarantee about destructors during stack unwinding, and what does it guarantee about ordinary statements? (Section 2.)
-
What does each smart pointer express —
unique_ptr,shared_ptr,weak_ptr— and which is the default? Give the reason in one clause each. (Sections 4, 6, 7.) -
Why can a
unique_ptrnot be copied, and what replaces the copy? What was wrong withauto_ptrthat made the language remove it? (Section 4.) -
A
shared_ptris "thread-safe". Say precisely what is safe and what is not. (Section 6.)
Part B — Prediction and explanation¶
- Predict the output of this program, and say why.
#include <iostream>
#include <memory>
struct Node {
std::shared_ptr<Node> next;
~Node() { std::cout << "dtor\n"; }
};
int main() {
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->next = b;
b->next = a;
}
Answer. It prints nothing. No destructor runs, because each node's reference count is held
above zero by the other's owning link: when main ends, a and b go away, each count falls from
2 to 1, and stops there. The objects are never destroyed and the memory is never released — a
reference cycle.
A full-credit answer adds two things. First, that this is not an error any sanitizer or leak
checker will report: the memory is reachable, so a leak checker classifies it as still reachable
and a sanitizer sees nothing invalid. Second, the fix: one link becomes std::weak_ptr<Node>, after
which the counts reach zero and the destructors run in reverse order of the remaining ownership.
- Predict the counters. A shared object is created with
make_shared, and twoshared_ptrs and oneweak_ptrrefer to it. The first owner is reset, then the second. Predictuse_count()after each step, and say exactly when the destructor runs.
auto p = std::make_shared<Item>(); // use_count ___
auto q = p; // use_count ___
std::weak_ptr<Item> w = p; // use_count ___
p.reset(); // use_count ___ destructor? ___
q.reset(); // use_count ___ destructor? ___
std::cout << w.expired() << '\n'; // prints ___
Answer. 1, 2, 2, 1, 0. The destructor runs once, after the last owning pointer is
released — that is, at q.reset(). The weak_ptr never affects the count, so w.expired() prints
1 (true) at the end, and w.lock() would return an empty shared_ptr.
A full-credit answer notes two further facts. use_count() on a weak_ptr and on a destroyed object
is diagnostic only, never something to branch on. And between q.reset() and the expired() check,
nothing needs to be got right — expired() is safe to call at any time, which is precisely what
makes weak_ptr the tool for the cache design in Challenge 1.
- Explain this design decision. A colleague changes a function from
void process(const Item& item)tovoid process(std::shared_ptr<Item> item), saying "it makes ownership explicit". Evaluate that claim.
Answer. The claim is wrong in a specific, checkable way. The function still does not own the item
— it does not store it — so the signature now promises something false. What changed is the cost: the
caller must supply a shared_ptr, every call performs two atomic reference-count operations, a caller
that has a plain Item (or a unique_ptr<Item>) can no longer call it at all, and a temporary owner
now exists for the duration of the call.
A full-credit answer names the three signatures that would be right — const Item& to read, Item&
to modify, Item* if absence is possible — and the conditions under which the colleague would be
right: if the function stores or returns the object, or if the callee must keep it alive beyond the
call, or if the caller genuinely shares ownership with others. It also notes what "explicit
ownership" really looks like: a type that states the claim, like unique_ptr<T> by value for a
transfer, not a shared_ptr passed by value as a formality.
- Explain a silent failure. This code compiles with
-Wall -Wextra -Werrorand deletes the object twice. Why is the compiler silent, and what are the two fixes?
Answer. Constructing a shared_ptr from a raw pointer creates a new control block. Called
twice on the same object, the program has two counts, each of which will reach zero independently,
and therefore two deletes of one object. The compiler is silent because both constructions are
legal — the language has no way to know that the two raw pointers refer to the same object.
Both fixes change the design rather than the syntax. At the class level: inherit
std::enable_shared_from_this<Surface> and return shared_from_this(), which reuses the existing
control block. At the call site: never wrap a raw pointer that some shared_ptr already
manages — copy an existing shared_ptr instead, and if a factory hands out raw pointers to a shared
object, that factory is the bug.
A full-credit answer adds why this is a module-level lesson rather than a trivia item: the mistake produces two owners of one object from a perfectly ordinary line, which is exactly the failure Module 10 taught you to fear, reintroduced by a type whose purpose is to prevent it.
Marking yourself¶
| Score | What it means |
|---|---|
| ≥ 80%, every miss explained | Move on to Module 12 |
| 60–79% | Re-read sections 4 to 8 of Concepts, redo the cycle and control-block experiments, then retake in a week |
| < 60% | Redo Example 1 and Example 4 from the worked examples, with counters, before continuing |
This module's material is not optional for what follows. Module 13 builds exception safety directly on RAII — if the destructor is not the release point, the failure path cannot be made correct at all — and Module 14's containers, the capstone's architecture and every factory in Module 15 assume you can answer who owns this? without thinking about it.
A quiz is diagnostic, not a grade. Keep the list of misses and reread it before Lab 11.
Next: Summary — the module compressed to what you must retain.