Rule A12-0-1 (required, implementation, automated)
If a class declares a copy or move operation, or a destructor, either via “=default”, “=delete”, or via a user-provided declaration, then all others of these five special member functions shall be declared as well.
Rationale
The semantics of five of the special member functions, the copy constructor, the move constructor,
the copy assignment operator, the
move
assignment
operator, and the destructor, are closely related to each other. If, for example, there is need to provide a nondefault destructor to release a resource, special handling usually also needs to be added in the copy and move operations to properly handle this resource. Language rules exist to generate the special member functions under certain conditions. For historical reasons, these language rules are not entirely consistent. For example, the presence of a destructor does not prevent the compiler from generating copy operations. However, it prevents the move operations from being generated. Thus
it is required, in order to maintain consistency and document the programmer’s intent, that either none or all of the five functions are declared. This rule is also known as “the rule of zero”, or “the rule of five” respectively. It is highly recommended to design classes in a way that the rule of zero can be followed. Note that the default constructor (which is also a special member function) is not part of this rule. The presence of any user-declared constructor inhibits the generation of the default constructor. Therefore, if a user-declared constructor is present, it may be necessary (depending on requirements) to also declare the default constructor. However, the presence of a user-declared default constructor does not inhibit the generation of the other five special member functions. This rule therefore allows to follow the rule of zero when the class only has a user-declared default constructor (and possibly one or more constructors which are not special member functions).
Example
// $Id: A12-0-1.cpp 309769 2018-03-01 17:40:29Z jan.babst $
#include <string>
namespace v1
{
// Class is copyable and moveable via the compiler generated funtions.
// Compliant - rule of zero.
class A
{
private:
// Member data ...
};
} // namespace v1
namespace v2
{
// New requirement: Destructor needs to be added.
// Now the class is no longer moveable, but still copyable. The program
// still compiles, but may perform worse.
// Non-compliant - Unclear if this was the developers intent.
class A
{
public:
~A()
{
// ...
}
private:
// Member data ...
};
} // namespace v2
namespace v3
{
// Move operations are brought back by defaulting them.
// Copy operations are defaulted since they are no longer generated
// (complies to A12-0-1 but will also be a compiler error if they are needed).
// Default constructor is defaulted since it is no longer generated
// (not required by A12-0-1 but will be a compiler error if it is needed).
// Compliant - rule of five. Programmer’s intent is clear, class behaves the
// same as v1::A.
class A
{
public:
A() = default;
A(A const&) = default;
A(A&&) = default;
~A()
{
// ...
}
A& operator=(A const&) = default;
A& operator=(A&&) = default;
private:
// Member data ...
};
} // namespace v3
// A class with regular (value) semantics.
// Compliant - rule of zero.
class Simple
{
public:
// User defined constructor, also acts as default constructor.
explicit Simple(double d = 0.0, std::string s = "Hello")
: d_(d), s_(std::move(s))
{
}
// Compiler generated copy c’tor, move c’tor, d’tor, copy assignment, move
// assignment.
private:
double d_;
std::string s_;
};
// A base class.
// Compliant - rule of five.
class Base
{
public:
Base(Base const&) = delete;
Base(Base&&) = delete;
// see also
// see also
A12-8-6
A12-8-6
virtual ~Base() = default;
// see also A12-4-1
Base& operator=(Base const&) = delete; // see also A12-8-6
Base& operator=(Base&&) = delete;
// see also A12-8-6
// Declarations of pure virtual functions ...
protected:
Base() = default; // in order to allow construction of derived objects
};
// A move-only class.
// Compliant - rule of five.
class MoveOnly
{
public:
MoveOnly();
MoveOnly(MoveOnly const&) = delete;
MoveOnly(MoveOnly&&) noexcept;
~MoveOnly();
MoveOnly& operator=(MoveOnly const&) = delete;
MoveOnly& operator=(MoveOnly&&) noexcept;
private:
// ...
};
See also
C++ Core Guidelines [11]: C.21: If you define or =delete any default operation, define or =delete them all. C++ Core Guidelines [11]: C.81: Use =delete when you want to disable default behavior (without wanting an alternative).