If you define or `=delete` any copy, move, or destructor function, define or `=delete` them all
Reason
The semantics of copy, move, and destruction are closely related, so if one needs to be declared, the odds are that others need consideration too.
Declaring any copy/move/destructor function, even as =default or =delete, will suppress the implicit declaration of a move constructor and move assignment operator. Declaring a move constructor or move assignment operator, even as =default or =delete, will cause an implicitly generated copy constructor or implicitly generated copy assignment operator to be defined as deleted. So as soon as any of these are declared, the others should all be declared to avoid unwanted effects like turning all potential moves into more expensive copies, or making a class move-only.
Example, bad
struct M2 { // bad: incomplete set of copy/move/destructor operations
public:
// ...
// ... no copy or move operations ...
~M2() { delete[] rep; }
private:
pair<int, int>* rep; // zero-terminated set of pairs
};
void use()
{
M2 x;
M2 y;
// ...
x = y; // the default assignment
// ...
}
Given that "special attention" was needed for the destructor (here, to deallocate), the likelihood that the implicitly-defined copy and move assignment operators will be correct is low (here, we would get double deletion).
Note
This is known as "the rule of five."
Note
If you want a default implementation (while defining another), write =default to show you're doing so intentionally for that function. If you don't want a generated default function, suppress it with =delete.
Example, good
When a destructor needs to be declared just to make it virtual, it can be defined as defaulted.
class AbstractBase {
public:
virtual void foo() = 0; // at least one abstract method to make the class abstract
virtual ~AbstractBase() = default;
// ...
};
To prevent slicing as per C.67, make the copy and move operations protected or =deleted, and add a clone:
class CloneableBase {
public:
virtual unique_ptr<CloneableBase> clone() const;
virtual ~CloneableBase() = default;
CloneableBase() = default;
CloneableBase(const CloneableBase&) = delete;
CloneableBase& operator=(const CloneableBase&) = delete;
CloneableBase(CloneableBase&&) = delete;
CloneableBase& operator=(CloneableBase&&) = delete;
// ... other constructors and functions ...
};
Defining only the move operations or only the copy operations would have the same effect here, but stating the intent explicitly for each special member makes it more obvious to the reader.
Note
Compilers enforce much of this rule and ideally warn about any violation.
Note
Relying on an implicitly generated copy operation in a class with a destructor is deprecated.
Note
Writing these functions can be error-prone. Note their argument types:
class X {
public:
// ...
virtual ~X() = default; // destructor (virtual if X is meant to be a base class)
X(const X&) = default; // copy constructor
X& operator=(const X&) = default; // copy assignment
X(X&&) noexcept = default; // move constructor
X& operator=(X&&) noexcept = default; // move assignment
};
A minor mistake (such as a misspelling, leaving out a const, using & instead of &&, or leaving out a special function) can lead to errors or warnings. To avoid the tedium and the possibility of errors, try to follow the rule of zero.
Enforcement
(Simple) A class should have a declaration (even a =delete one) for either all or none of the copy/move/destructor functions.