Skip to content

← Copy, Move, and the Rule of Zero step 3 of 5

Medium Primitives

The line that does nothing, twice a second

Every class has five special member functions. You get them whether or not you write them:

~T();                          // destructor
T(const T&);                   // copy constructor
T& operator=(const T&);        // copy assignment
T(T&&) noexcept;               // move constructor
T& operator=(T&&) noexcept;    // move assignment

The compiler generates each one memberwise: destroy each member, copy each member, move each member. For a class whose members are std::vector, std::string, std::unique_ptr — types that already manage themselves — the generated versions are exactly right, and better than what you would write, because they cannot drift out of sync with the member list when someone adds a field.

The rule of zero: if your class does not directly own a raw resource, declare none of the five.

The trap, which is the reason this problem exists

These five are not independent. Declaring one changes what you get for the others, and the rule that bites is:

Declaring a destructor — or a copy operation — suppresses the implicit move constructor and move assignment operator.

Suppressed, not deleted. The class still compiles, still copies, and std::move on it silently selects the copy constructor, exactly as in the previous problem. Your type has quietly become expensive, and nothing about the destructor you wrote suggests it.

And = default does not save you. This:

~Session() = default;

is a user-declared destructor. It generates the same code the compiler would have, and it suppresses moves just as thoroughly as a hand-written one. The fix is not to default the line. The fix is to delete the line.

The rule of five, in one sentence

If you genuinely must write one of the five — because your class owns something raw — write all five, because the compiler has stopped helping. That is the next problem. This one is about the far more common case, where the right answer is to write none.

Your task

collect is given and already correct: it moves every session into a new vector. Do not change it.

std::vector<Session> collect(std::vector<Session>& sessions);

Session holds a Counted member, which counts the copies and moves made of it, and the harness reports both. Right now every session is copied, and one line in Session is the reason.

Find it, and get copies to zero without changing collect or Counted.