Skip to content

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.

construct first
construct second
Machine body
Machine body destroyed
destroy second
destroy first

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