Skip to content

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

  1. 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.

  2. Why must a const member be initialised in the initialiser list? Name the compiler's error.

  3. 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";
}
  1. 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?

  2. A deleter overload is written as Loan(bool) = delete; and then Loan loan("alice"); fails to compile. Explain why, and give the fix.

  3. class A { A(); A(int days = 14); }; — how many default constructors does A have, and why is that a problem rather than a curiosity?

  4. A class declares members in the order int b_; int a_; and writes Minimal() : a_(1), b_(a_) {}. What is b_? What does the compiler say?

  5. State the rule for when a class needs a destructor. Then state what else it needs when that rule applies, and why.

  6. 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 calls return before 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.

make a
copy a*
show a*
kill a*
make r
done
kill r
kill a

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 in b. So there is one make r line and no copy: const Note b = make(); produces exactly one construction.
  • No kill for the temporary appears earlier than the end of main, because there was no temporary.
  • Destruction order is the reverse of construction: r was constructed after a, so r is 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