Skip to content

Module 5 — Self-Assessment

Nine questions. The two predictions are worth more than the recall questions, and an answer that states a rule without being able to apply it scores partial credit at best.

Write your answers down before reading the answers section. A question you answer in your head feels known; the same question answered on paper is where the gaps appear.


The questions

  1. Why must at least one operand of an overloaded operator be a class or enumeration type?

  2. Name three operators that cannot be overloaded, and explain the reason in terms of name resolution rather than "the standard forbids it".

  3. Why is operator<< almost always a free function, and what exactly does returning std::ostream& enable?

  4. What is a strict weak ordering? Name the property that beginners most often violate, and state what the standard says happens when a comparator is not one.

  5. Predict. What does this print, and why?

#include <iostream>
struct Track {
    int id;
    Track(int i) : id(i) {}
    Track(const Track& o) : id(o.id) { std::cout << "copy\n"; }
    Track& operator=(const Track& o) { id = o.id; std::cout << "assign\n"; return *this; }
};
int main() {
    Track a(1);
    Track b = a;
    Track c(2);
    c = a;
}
  1. What does a static data member belong to, and what must be written outside the class? Name the C++17 alternative and say what it removes.

  2. Why does a static member function have no this, and what follows from that? Give two consequences.

  3. Predict. std::sort is given a comparator that returns true for equal elements. Does it fail to compile, run incorrectly, or both — and what is the honest answer about the order it produces?

  4. When is a static local variable the right tool, and what does it cost? How does it differ from a static data member?


Answers

1. Because otherwise the meaning of the language's own operators could be changed. 1 + 2 must always be 3; if a user-defined operator+ could take two ints, every program's arithmetic would become unknowable from its own source text.

Full credit also notes the corollary: this is why Money operator+(int, Money) must be a free function — the int is on the left, and only a free function can serve when one operand is a built-in type you do not own.

2. ::, ., .* (and ?:, sizeof, typeid, alignof, the named casts). The first three are how names are resolved: a.b decides which b you mean, and A::b decides which scope. If those could be redefined, a program's meaning would depend on its own operators, and no one — human or compiler — could parse it reliably. ?: is excluded for similar reasons of control-flow structure.

Full credit distinguishes these from the operators that can be overloaded and should not be: &&, ||, ,, & — each carries a guarantee (short-circuiting, sequencing, taking an address) that overloading destroys silently.

3. Because the left operand of std::cout << book is a std::ostream, and a member function can only have *this as its left operand — a type you cannot add members to. Returning std::ostream& makes the result of one << usable as the left operand of the next, which is what std::cout << a << b requires; returning void compiles for one object and fails for two.

Full credit adds: the operator usually needs no friend if the type has an honest accessor, as Money does; operator>> is the one that usually needs access, because it must write.

4. A relation where: irreflexive (a < a is false), asymmetric (a < b implies b < a is false), transitive (a < b and b < c imply a < c), plus transitivity of the "equivalent" relation. The violated property is irreflexivity — from writing <=, or from comparing several fields where one uses <=. The standard calls a violation undefined behaviour: the sort may work, may give a wrong order, may crash, and may differ between runs.

Full credit states that a wrong order is the lucky outcome, which is why the comparator test is a property test rather than an eyeball test.

5. copy then assign.

Track b = a; is initialisation syntax: b does not exist yet, so it is constructed, by the copy constructor. c = a; is assignment, on the already existing c. The = in both lines looks the same and means different functions.

Full credit gives the rule that decides it: did the object already exist before this line?

6. The class, not any object: one storage location shared by every instance, existing even when no object does. It is declared in the class and must be defined once outside it — int Ticket::live_ = 0; — unless it is inline (C++17), which defines it in the class and removes that line. A constexpr member of integral or enumeration type may be initialised in the class.

Full credit names the consequence of omitting it: a link error naming the member (undefined reference to 'Ticket::live_'), not a compile error.

7. It is called on the class (Ticket::liveCount()), so there is no object for this to point at. Consequences: (a) it cannot read or write a non-static member — error: invalid use of member 'Book::id_' in static member function; (b) it cannot be const, because const qualifies *this and there is nothing to qualify. Full credit adds a third: it is therefore the right home for class-level facts and for named constructors (Book::fromIsbn(...)), which can be called before any object exists.

8. It compiles, and it runs. It does not fail to compile, and it does not reliably produce a "different but well-defined" order: an invalid comparator makes the ordered algorithms' behaviour undefined, so the result may be a wrong order, an out-of-bounds access, or a crash. The honest answer about the order is that no order is promised at all, and a wrong answer on your machine today is not evidence about the code.

Full credit notes both halves — that the compiler is silent, and that "it worked for me" is not a defence against undefined behaviour.

9. Right tool: a counter or cache that must survive between calls, inside a function with no object to attach the state to — or a lazily built lookup table. Cost: genuine global state with narrow visibility, which is harder to reset in tests and invisible from a function's signature. Difference: a static local is one per function and visible only there; a static data member is one per class and part of the type's documented behaviour.

Full credit adds the thread-safety fact: since C++11, the initialisation of a function-local static is thread-safe and happens exactly once.


Marking yourself

Two points per question, one for the fact and one for the consequence or the prediction being right.

Score What it means
≥ 80%, every miss explained Move on to Module 6
60–79% Re-read the sections behind the misses, retake in a week
< 60% Redo the worked examples and Lab 5 before going further

A quiz is diagnostic, not a grade. The useful output is the list of concepts that need another look — and the honest sentence about why you got each one wrong: never learned it, learned it as a rule without a consequence, or knew it and misread.


Re-test schedule

When What to redo
Tomorrow Questions 4, 5 and 8 — the three that are predictions or consequences
In a week Questions 1, 2 and 7, in a fresh file, without notes
In a month The whole quiz, and the comparator property test written from memory

Question 8's answer is the one to keep: it is the difference between "this code works" and "this code is defined". Everything in Module 9 and Module 11 assumes you hold that distinction.


Ready to move on when

  • [ ] You scored ≥ 80%, and any miss has a written explanation, not a shrug
  • [ ] You can answer question 5 from the rule, without reciting the program
  • [ ] You wrote the twenty-line comparator property test from memory at least once
  • [ ] You have run an invalid comparator and recorded what it did — not just read about it
  • [ ] The skills checklist on the module page is ticked with evidence

Next: Summary — the module compressed to what you must retain. · All modules