Module 10 — Self-Assessment¶
Ten questions. Part A checks that you have the vocabulary; Part B checks that you can predict — which is the only part that tells you whether you can use this material.
Answer on paper, or in a scratch file. Then mark yourself honestly, and note which section of Concepts each miss points back to.
Part A — Recall and comprehension¶
-
What does the compiler-generated copy constructor do for a class with a
std::stringmember and astd::vector<int>member? Is that correct, and why? (See section 3.) -
State the Rule of Three, the Rule of Five and the Rule of Zero, and say what the progression between them is telling you. (Section 6 and section 9.)
-
What does
std::move(x)do? Name its return type, and say what decides whether a move actually happens afterwards. (Section 7.) -
What may you legitimately do with a moved-from object? Give three operations that are guaranteed to work and one that is not. (Section 7.)
-
Why should a move constructor be
noexcept, and what exactly goes wrong if it is not? Be specific about which standard type cares and why it cares. (Section 8.)
Part B — Prediction and explanation¶
- Predict the output of this program, and then say what is wrong with it.
#include <iostream>
struct Own {
int* p;
explicit Own(int v) : p(new int(v)) { std::cout << "ctor\n"; }
~Own() { std::cout << "dtor\n"; delete p; }
};
int main() {
Own a(1);
Own b = a;
*b.p = 99;
std::cout << *a.p << '\n';
}
Answer. It prints ctor, then 99, then two dtor lines — and the second dtor is a double
free. Own b = a; invoked the compiler-generated copy constructor, which copied the pointer, so
both objects point at one int. Writing through b changed what a sees (that is why 99
prints). The undefined behaviour begins at the second delete; the program may crash there, or
later, or not at all in this run, and "it did not crash" is not evidence.
A full-credit answer also says what the fix is: a deep copy constructor (p(new int(*other.p))),
or = delete, or a Rule of Zero redesign with a std::unique_ptr<int> member — in which case the
copy would have been deleted for you and this program would not have compiled.
- Predict the counters. A
Trackedclass with anoexceptmove constructor and aconst std::string name_member. Predict the result of each line, then say which one is the trap.
Tracked a("a");
Tracked b = a; // (1)
Tracked c = std::move(a); // (2)
Tracked& alias = b;
b = std::move(alias); // (3)
Answer. (1) is a copy, one copy, zero moves. (2) is a move — one move, zero copies — and a is
left valid but unspecified. (3) is the trap, and it has two parts: the move happens (zero copies,
one move), and because the source and destination are the same object, the string member is moved
into itself, which may leave b.name_ empty. It compiles, it runs, and the compiler says nothing.
The const on name_ would matter in a different line: std::move(c.name_) selects the copy
constructor, because const std::string&& cannot bind to std::string&&. Both halves of the
answer are the point: a const member is copied forever, and a self-move is silent.
- Predict what changes. A working class has a destructor, no copy operations declared, and no
move operations. You add
Widget(Widget&&) noexcept = default;. What does the class's interface look like before and after, and what will existing code do differently?
Answer. Before: the copy constructor and copy assignment are generated; no move operations
exist, so every "move" is a copy. After: the move constructor exists, the copy constructor and
copy assignment are deleted (implicitly, because a move operation was declared), and the move
assignment operator still does not exist. So the class goes from "copyable and never moved" to
"move-only", and any existing code that copied a Widget now fails to compile with
use of deleted function. A full-credit answer adds the fix: declare whichever of the five you
actually want, explicitly.
- Explain this measurement. The same class, twice; only the move constructor's exception specification differs. Why does the looser signature produce more copies?
Item<true>, noexcept move: constructions 15, copies 0, moves 7
Item<false>, throwing move: constructions 15, copies 7, moves 0
Answer. Both versions were stored in a std::vector that reallocated three times while growing
to eight elements. On reallocation the vector must transfer the seven existing elements, and it
offers the strong guarantee: if the transfer throws, the vector must be unchanged. Moving elements
out of the old buffer destroys the originals, so a throw part-way through would leave the vector
unrecoverable; Item<false> therefore cannot be moved safely, and the vector copies the elements
instead. Item<true> promises the move cannot throw, so the vector moves. Both classes behave
identically and print the same results — the difference is seven copies, each of which allocates if
the element owns a string, and no diagnostic anywhere.
A full-credit answer names the mechanism too: std::move_if_noexcept, which returns an rvalue
reference when moving is safe and a const lvalue reference otherwise.
- Explain a design decision. A colleague proposes giving every class in your library a destructor, a copy constructor, a copy assignment operator, and both move operations — "so we are explicit about everything". Argue both sides, then decide.
Answer. For: the compiler's generation rules are subtle, and writing all five removes the
surprise. A class with all five written out is fully predictable, and noexcept can be stated
where it matters. Against: five functions per class is a great deal of code, most of it identical
to what the compiler would have generated; every hand-written one is a chance to get self-assignment
or the allocation ordering wrong, and the compiler's versions cannot forget a member added later —
whereas a hand-written member-wise copy silently can. Writing a destructor suppresses the move
operations, which is the opposite of what "being explicit" intends.
A full answer resolves it in the direction of the Rule of Zero: make the class own no resource
directly, let standard members own things, and declare none of the five. Reserve hand-written
special members — and explicit = default where you want the generated form with a change to the
group's composition — for the few classes that genuinely own a non-memory resource. A weaker but
acceptable answer is "write them all, correctly, and add static_assert tests for the properties
you care about"; what makes it acceptable is that it names the maintenance cost it accepts.
Marking yourself¶
| Score | What it means |
|---|---|
| ≥ 80%, every miss explained | Move on to Module 11 |
| 60–79% | Re-read Concepts sections 5 to 9, redo Lab 10's requirements 5 to 8, then retake in a week |
| < 60% | Redo the double-free experiment from the start, with counters, before going further |
This module is a prerequisite for everything after it. Compiling and running is not enough here: if you cannot predict the counters, the class is not understood yet — and every later module in this course builds a class that owns something.
A quiz is diagnostic, not a grade. The useful output is the list of concepts that need another look, so keep that list and check it again before the capstone.
Next: Summary — the module compressed to what you must retain.