59 Intermediate The Hundred
The Destructor That Deleted Your Move
two structs, one extra line, and only one of them moves
struct Payload {
Payload() = default;
Payload(const Payload&) { std::cout << "copy\n"; }
Payload(Payload&&) noexcept { std::cout << "move\n"; }
};
struct Box {
Payload contents;
};
struct Chest {
Payload contents;
~Chest() = default; // Chest owns nothing, but be explicit about cleanup
};
template <class T> void ship(T) {}
int main() {
Box b;
Chest c;
std::cout << "Box -> ";
ship(std::move(b));
std::cout << "Chest -> ";
ship(std::move(c));
}
Run it. Do both hand-offs move?
Answer
Only the Box moves. Chest has no move constructor at all — that defaulted destructor took
it away, and std::move fell back on the copy.
Why¶
A class gets an implicit move constructor and move assignment only if it declares none of the
other special members — and a destructor counts, even = default. Chest declares one, so it
has no moves; the copy constructor is still there, and it binds std::move(c)'s rvalue without
complaint. Even the traits play along: std::is_move_constructible_v<Chest> is true — it
only asks whether some constructor accepts an rvalue — while the nothrow version is
false, so std::vector<Chest> copies on reallocation too (entry 82). -Wall -Wextra say
nothing (GCC 11), and -Wdeprecated-copy-dtor (entry 58) ignores a defaulted destructor.
The fix¶
Delete the line. A class that declares no special members gets all five, correct and free:
If the destructor must stay, = default the rest — and mind the cascade: restoring the two
moves deletes the two copies, and any constructor you declare removes Chest().
Takeaway: touching one special member silently changes which others exist — write all five, or none.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo