Module 3 — Self-Assessment¶
Nine questions. Closed-book, in writing, about twenty-five minutes. Several ask you to predict, and for those the answer includes the reasoning — a correct number with a wrong reason counts as a miss, because the reason is what transfers to the next program.
The quiz covers the same ground in multiple-choice form. This page is the one to use straight after the module, because every question names the section to re-read when you miss it.
The questions¶
-
In what order does an object get built, and in what order is it destroyed? Name the three construction steps and the two rules about ordering.
-
Why must a
constmember be initialised in the initialiser list? Name the compiler's error. -
Predict the exact output, then run it.
#include <iostream>
#include <string>
class Note {
public:
Note(const std::string& text) : text_(text) {
std::cout << "make " << text_ << '\n';
}
Note(const Note& other) : text_(other.text_ + "*") {
std::cout << "copy " << text_ << '\n';
}
~Note() { std::cout << "kill " << text_ << '\n'; }
const std::string& text() const { return text_; }
private:
std::string text_;
};
void show(Note note) { std::cout << "show " << note.text() << '\n'; }
Note make() { return Note("r"); }
int main() {
Note a("a");
show(a);
const Note b = make();
std::cout << "done\n";
}
-
How many constructions does
const Note b = make();produce under C++17, and how many under C++14? Why is the course's build pinned to C++17? -
A deleter overload is written as
Loan(bool) = delete;and thenLoan loan("alice");fails to compile. Explain why, and give the fix. -
class A { A(); A(int days = 14); };— how many default constructors doesAhave, and why is that a problem rather than a curiosity? -
A class declares members in the order
int b_; int a_;and writesMinimal() : a_(1), b_(a_) {}. What isb_? What does the compiler say? -
State the rule for when a class needs a destructor. Then state what else it needs when that rule applies, and why.
-
A local object is created inside
{ }in the middle of a function. When exactly is it destroyed — and what happens to it if the function callsreturnbefore reaching the closing brace?
Answers¶
1. Base class, then data members in declaration order, then the constructor body. Destruction is the exact reverse: body's locals in reverse order of creation, members in reverse declaration order, then the object itself. The two ordering rules: members are initialised in declaration order, not the order written in the list, and destruction is always the reverse of construction, per scope.
2. Because the body runs after all members have been constructed and initialised, so
assigning in the body is not initialisation at all — and a const member cannot be
assigned once it exists. The compiler reports
uninitialized const member in 'const int' [-fpermissive], with a note naming the member,
and assignment of read-only member. The same reasoning applies to a reference member
(uninitialized reference member in 'int&').
3.
Four things are being tested.
show(a)takes its parameter by value, so a copy is made, printed and destroyed — the three lines with the asterisk.make()returns an unnamed value, and under C++17 it is constructed directly inb. So there is onemake rline and no copy:const Note b = make();produces exactly one construction.- No
killfor the temporary appears earlier than the end ofmain, because there was no temporary. - Destruction order is the reverse of construction:
rwas constructed aftera, soris destroyed first.
If you predicted copy r* and an extra kill, your model is C++14's — see question 4.
That is the single most useful thing this question can find out about you.
One honest complication: building this same program with -std=c++14 on a modern
compiler produced the identical trace, because compilers elide the copy when they are
allowed to. The difference between the two standards is therefore not what you observe
today — it is what you are guaranteed. If that distinction seems pedantic, ask what
happens when you switch compiler or turn optimisation off, and which of the two you would
want your prediction to rest on.
4. Under C++17, one construction and no copy: mandatory copy elision requires the
returned prvalue to be constructed directly in the caller's object. Under C++14, the
compiler is permitted to elide the copy — and every mainstream compiler does, which is
why the C++14 build of this very program prints the same trace — but it is not required
to, and the move-or-copy constructor must still be callable or the program is ill-formed.
This is the useful form of the point: C++17 turns a near-universal optimisation into a
guarantee. The course pins -std=c++17 so that a prediction exercise rests on the
language rather than on which compiler and which flags you happened to use.
5. Because const char* converts to bool through a standard conversion, while
const char* converts to std::string through a user-defined conversion, and the
standard requires a standard conversion to be preferred. So the deleted bool overload is
selected — the compiler is being consistent, and the deletion was aimed at the wrong type.
Fix: Loan(void*) = delete;, which refuses Loan broken(nullptr); while leaving
Loan loan("alice"); reaching the std::string overload. examples/module-03/04-constructor-overloads/main.cpp
contains both versions, with the messages.
6. Two — A() and A(int days = 14), because a constructor callable with no
arguments is a default constructor whatever its parameter list looks like. Two default
constructors make A a; ambiguous (error: call of overloaded 'A()' is ambiguous), and
more importantly they suppress the compiler's generated one, so the type has no "obvious"
default state at all. The fix is to keep one and delegate:
A() : A(14) {} and A(int days).
7. b_ is initialised from a_ before a_ has been initialised — because
initialisation follows declaration order and b_ is declared first. b_ therefore holds
an indeterminate value (reading it is undefined behaviour), and the compiler warns:
'Minimal::b_' will be initialized after / 'int Minimal::a_' / when initialized here
(-Wreorder). The course's build treats that warning as an error, which is why the fix —
declaring members in the order you initialise them — is enforced rather than merely
recommended.
8. A class needs a destructor when it owns a resource that must be released —
something the generated destructor cannot release by destroying members (a raw pointer from
new, a file handle, a lock). When that applies, the class also needs a copy constructor
and a copy assignment operator, because the generated versions copy the handle rather
than the resource: two objects end up owning one thing, and both will release it. That is a
double free, and it is Module 10's opening problem. (The better answer, reached in Module
11, is to make the members own the resource so none of the three is needed.)
9. At the closing brace of that scope, not at the end of the function. This is the point of automatic storage duration: an object's lifetime is bounded by its scope, not by the function containing it. And there is no exception for early return — if the function returns before the closing brace, the brace is still reached: the destructor runs during the return. The same is true when an exception unwinds the stack (Module 13), which is the property the whole of RAII depends on.
Marking yourself¶
One mark per question. For question 3, award the mark only if all of the trace lines and their reasons are right — a correct trace with a wrong explanation is a miss, and questions 3 and 4 together are worth more than a mark each in terms of what they tell you.
| Score | What it means | What to do |
|---|---|---|
| 9 | You can predict lifetimes rather than observe them | Move to Lab 3 if it is unfinished, then Module 4 |
| 7–8 | The mechanisms are there, one or two gaps | Re-read the section for each miss, and re-answer tomorrow |
| 5–6 | The syntax works; the ordering model does not | Re-read §§1, 4 and 7 of Concepts and redo warm-ups W1 and W3 |
| 0–4 | The module needs a second pass, and this is a common place to need one | Type out all four programs in Worked examples by hand, predicting first. Do not move on: Module 10 assumes you can already predict a trace |
Which section to re-read, by question:
| Missed | Re-read |
|---|---|
| 1 | Concepts §1 and §7 |
| 2 | Concepts §4, D1–D2 in Misconceptions |
| 3 | Worked examples, Example 1 and Example 3, then this question again |
| 4 | M8 in Misconceptions |
| 5 | M9 in Misconceptions |
| 6 | M4 in Misconceptions |
| 7 | M3 in Misconceptions |
| 8 | Concepts §5, then Example 5 |
| 9 | Concepts §7 |
Re-test schedule¶
| When | Do what |
|---|---|
| Tomorrow | Re-answer the questions you missed. Do not re-read first — the failure is informative |
| In a week | Question 3 again, with a different program: write your own five-line class with a by-value parameter and a returned value, predict, then run |
| In a month | Add a lifecycle counter to a class in whatever module you are working on, and predict the trace of a real session with it. If the prediction is right first time, this module is finished |
The week-three re-test is the one that matters. Predicting a trace for code you have never seen is the skill; predicting it for the module's own example is practice.
Ready to move on when¶
- [ ] You scored 7 or more, or re-answered the misses correctly
- [ ] Your Lab 3 prediction is written down, and you can explain every difference between it and the trace
- [ ] You can say, for any object in a program you wrote, when it is constructed and when it is destroyed
- [ ] You check the live counter out of habit, and it ends at zero
- [ ] You write an initialiser list without thinking, and the reorder warning has not appeared in a week
- [ ] You know which of your Module 2 classes would need a destructor if it owned something — and that none of them currently do
Next: Summary — what must be retained, and the checklist. · All modules