A class holds a heap buffer and works correctly. You add a destructor that frees the buffer and change nothing else. A vector of 200k of these now takes twice as long to build and the profiler shows copy constructor calls where moves used to be. Explain what the destructor did and give the minimal fix.

A class holds a heap buffer and works correctly. You add a destructor that frees the buffer and change nothing else. A vector of 200k of these now takes twice as long to build and the profiler shows copy constructor calls where moves used to be. Explain what the destructor did and give the minimal fix.

Approach: Ask which special member functions the compiler still generates once a destructor is user declared, and what overload resolution falls back to when the move members are absent.

A user declared destructor suppresses the implicit move constructor and move assignment operator, so every move request resolves to the copy constructor, and the minimal fix is to default the two move members explicitly. The language generates the implicit special members only while you declare none of the related ones, and declaring a destructor is enough to stop the move pair from being generated. Once the move constructor is absent, overload resolution binds the rvalue to the copy constructor, which is a valid conversion, so the code compiles and quietly copies. Vector growth calls the move constructor only when it is declared noexcept, so the two defaulted members should also be noexcept. The rule of five is the standard statement of this: declare all five or none of them.

Follow-up: Why does a vector fall back to copying even when a move constructor exists, if that move constructor is not marked noexcept?

Key concepts: rule of five, move constructor, implicit special members, overload resolution.