Module 9 — Self-Assessment¶
Ten questions. Parts A and B are closed-book; Part C wants a compiler. Answers state what a full-credit answer contains, not merely the right word.
Part A — Recall and comprehension¶
1. Name the four relationships in this module, and give the UML notation for each.
(Answer: composition — filled diamond at the owner's end; aggregation — hollow diamond; association — plain line with multiplicities; dependency — dashed arrow. Full credit: a one-sentence lifetime statement for the first two — composition means the part dies with the whole, aggregation means it does not — and the observation that the notation is only a promise: in code, the difference is whether the member is a value/owning smart pointer or a non-owning reference/pointer.)
2. A class holds a Logger four ways: as a value, as a pointer, as a reference, and as a
parameter of one function. For each, answer: does it own the logger, can it be absent, can it
be changed?
(Answer: value — owns, cannot be absent, replaced only by assigning the whole object; pointer — does not own, can be absent, can be reseated; reference — does not own, cannot be absent, cannot be reseated; parameter — does not own, cannot be absent, irrelevant because nothing is stored. Full credit: the extra consequence for the reference member — the class loses its assignment operator.)
3. Explain why the compiler deletes a class's assignment operator when the class has a non-static reference member.
(Answer: assignment means "make this object equal to that one", and that would require making
the member refer to the other object's target. A reference cannot be reseated, so the operation
has no implementation, and the compiler defines it as deleted rather than guessing. Full credit:
the message — use of deleted function 'T& T::operator=(const T&)' with the note that it is
implicitly deleted and the reason — plus the observation that copy construction is still
available.)
4. Name five ways to expose an owned collection read-only, and one that is wrong.
(Answer: const container&; const T& at(i); begin()/end() returning const iterators;
snapshot() returning a copy; forEach(callback). The wrong one is a public non-const
reference to the container. Full credit: for each, whether the caller depends on the container's
type, and whether the returned data can outlive the owner.)
5. What is a forward declaration, what is it enough for, and what does it not allow?
(Answer: class Item; names a class without including its definition. It is enough for
pointer and reference members, and for declaring parameters and return types. It is not enough
for a value member (field 'x' has incomplete type), for calling a member function
(invalid use of incomplete type), or for inheriting. Full credit: the two sentences that make
it useful — headers declare, source files include — and the note that it also shortens compile
times.)
Part B — Prediction and explanation¶
6. What does this print, and why?
struct Part {
const char* name;
explicit Part(const char* n) : name(n) { std::cout << "construct " << name << '\n'; }
~Part() { std::cout << "destroy " << name << '\n'; }
};
class Machine {
public:
Machine() { std::cout << "Machine body\n"; }
~Machine() { std::cout << "Machine body destroyed\n"; }
private:
Part first{"first"};
Part second{"second"};
};
int main() { Machine machine; }
Answer.
Members are constructed in declaration order before the outer constructor body; destroyed in the reverse order after it. So the output reads as a mirror around the body lines. Full credit: the six lines in order and the sentence that the destructor body runs before the members are destroyed — which is why a destructor may still use its members safely.
7. This class holds a value member and a pointer member. Which lines fail to compile, and what does the compiler say?
class Session {
public:
Session(Logger& logger, int id) : logger_(logger), fallback_(&logger), id_(id) {}
Session& operator=(const Session&) = default;
private:
Logger& logger_;
Logger* fallback_;
int id_;
};
int main() {
Logger a("a");
Logger b("b");
Session first(a, 1);
Session second(a, 2);
first = second;
}
Answer. The assignment fails:
error: use of deleted function 'Session& Session::operator=(const Session&)'
note: 'Session& Session::operator=(const Session&)' is implicitly deleted because the
default definition would be ill-formed:
error: non-static reference member 'Logger& Session::logger_', cannot use default
assignment operator
Note that operator= was written as = default — which does not help, because the compiler
still cannot generate a definition. fallback_ (a pointer) would be fine; the reference member
is the whole problem. Full credit: the failure, the reason, and one acceptable fix — write an
assignment that leaves logger_ alone and copies the rest, or change the member to a pointer
and answer the null question. Also worth a mark: the observation that = default on a deleted
operation is not a fix.
8. What does this print, and what is the design mistake?
class Roster {
public:
void add(std::string name) { names_.push_back(std::move(name)); }
std::vector<std::string>& names() { return names_; }
std::size_t size() const { return names_.size(); }
private:
std::vector<std::string> names_;
};
int main() {
Roster roster;
roster.add("Ayesha");
roster.names().clear();
std::cout << roster.size() << '\n';
}
Answer. It prints 0. There is no diagnostic: names() is a legal, ordinary accessor. The
mistake is the accessor itself — handing out the container means every invariant the class
states (and every function that depends on the contents, such as a report or a count) can be
invalidated by a caller that never mentions Roster. Full credit: the output, the absence of a
diagnostic, and the fix phrased as a capability — const std::string& at(i),
begin()/end(), snapshot(), or forEach — rather than as a const-ness tweak.
9. A Loan holds Member& member_ and Item& item_. A Library holds
std::vector<Member> members_, std::vector<Item> items_ and std::vector<Loan> loans_, in
that order. Name two lifetime problems this design has, and say what you would do about each.
Answer. (i) std::vector reallocates as it grows, invalidating every pointer and reference
into its elements — so a Loan created before a member is added can refer to a Member that
has moved, and a reference cannot be reseated. Fixes: reserve, a std::deque, indices, or
heap-allocated members owned by std::unique_ptr. (ii) Destruction order: loans_ is declared
last, so it is destroyed first — which is correct here, because a Loan destructor only
releases references; but if ~Loan ever used member_, it would be a bug. Fix: declare
owners before borrowers (as this design does) and say so in a comment, so the ordering is a
decision rather than a coincidence. Full credit: both, with the second framed as a rule about
declaration order rather than as a bug that is currently firing.
10. Correct this design, and say what you changed and why.
class CourseCatalog : public std::vector<Course> {
public:
Course& findOrCreate(const std::string& code) {
for (Course& course : *this) {
if (course.code() == code) return course;
}
push_back(Course(code));
return back();
}
};
Answer. Three defects, and each is sufficient on its own. (i) std::vector has no virtual
destructor, so delete through a std::vector<Course>* is undefined behaviour — Module 7. (ii)
push_back, clear, insert, erase, resize and operator[] are all public on a type whose
job is a catalogue with an identity rule (one course per code), so the rule is unenforceable.
(iii) every caller now depends on std::vector, so replacing the storage is a change to every
call site. The correction is composition: hold std::vector<Course> courses_; privately,
publish find(code), add(...), size() and forEach(...), and keep the "one per code"
invariant in the single function that inserts. Full credit: all three defects, the rewritten
class, and one sentence on what the rewrite cost — the forwarding functions, and the loss of
begin()/end() unless you add them deliberately.
Marking yourself¶
| Score | What it means |
|---|---|
| ≥ 80%, every miss explained | Move on to the next module |
| 60–79% | Re-read the sections behind the misses, then retake in a week |
| < 60% | Redo the worked examples and the lab before going further |
A quiz is diagnostic, not a grade. The useful output is the list of concepts that need another look. For this module that list is usually ownership wording ("borrows" is not a specification — outlives is) and collection access, because both fail silently.
Ready to move on when¶
- [ ] You scored ≥ 80%, and any miss has a written explanation, not a shrug
- [ ] Question 9 was answered with both lifetime problems, not just the reallocation
- [ ] You can draw an ownership map with multiplicities and lifetimes from memory
- [ ] You have re-expressed at least one relationship from a pointer member to a value member because the ownership was exclusive
- [ ] You have closed a second door to an invariant and recorded what it cost
- [ ] The skills checklist on the module page is ticked with evidence
Next: Summary — the module compressed to what you must retain. · All modules