This section is based on the seminal table by Howard Hinnant that shows the rules for special member functions introduced with C++11.1
Before C++11 the Rule of Three introduced by Scott Meyers ruled. However, there exists a lot of code, where even that was not applied consciously.
The old Rule of Three prohibits copying by declaring copy constructor and copy assignment as private:
member functions. From C++11 on you should define them as =delete;
instead regardless of their visibility.
C++03
|
C++11 and later
|
---|---|
|
|
Code that you do not write can not be wrong
You are defining a class type and need to consider the definition of the class’ special member functions.
Defining special member functions (constructors, destructor, copy and move operations) is hard to do correctly and also might result in less-than-optimal code, in contrast to the compiler provided ones.
The following forces make defining a class’ special member functions hard:
Select member variable types, so that the compiler provided copy and move operations and destructor are correct.
Applying the Rule of Zero provides you with the following benefits:
You have to consider the following liabilities from applying the Rule of Zero:
Desdemona is a tragic character in Shakespeare’s Othello. Don’t let your class be the tragic character in your code base.
You are defining a class type that requires you to declare or define a destructor, such as the base class of an object-oriented hierarchy, or a scope-based resource management (SBRM/RAII) class without the need for transfer of ownership or ability of copying the resource (Scoped Manager).
When a destructor is defined, a class type will no longer have compiler-provided move operations but still, wrongly, gets compiler-provided copy-operations. In C++03 that led to the Rule of Three saying to declare copy operations as private:
or protected:
to avoid copying.
The following forces make defining the special member functions for a class hard:
Define the move-assignment operator as deleted.
aka Rule of Unique.
Unique ownership of a resource is one of the key contributions of the Move Semantics introduced with C++11
You are defining a resource managing class type with unique ownership of the resource (Unique Manager) that requires you to define a destructor for releasing the resource.
When a destructor is defined, such a class type will have no move operations, and defaulted copy-operations that do the wrong thing.
The following forces make defining the special member functions for a unique manager class hard:
Define the following special member functions:
=default
creating an object in an empty moved-from state. To achieve that the member initializers should put the class’ members in a state that is equivalent to the moved-from state, e.g., by using an invalid value for the resource handle, or by having a boolean flag, denoting the moved-from state.swap
and namespace-level swap
functions, only if std::swap
would be less efficient.Either the member swap
or the move operations can be defined with the other. Doing both would lead to endless recursion:
struct X {
X(X&& x)noexcept
:member{std::move(x.member)} // move construct each member
{}
X& operator=(X&& r)noexcept {
this->swap(r); // recursion?
return *this;
}
void swap(X& r) noexcept {
X tmp{std::move(r)};
r = std::move(*this); // recursion!
*this = std::move(tmp);
}
Y member;
};
A namespace-level swap should always be implemented delegating to the member swap and it must be in the same namespace as the class definition to achieve the desired effects of argument dependent lookup (ADL).
void swap(X &l, X &r) noexcept {
l.swap(r);
}
std::move()
The standard library class templates unique_ptr
, unique_lock
, future
and the file stream classes fstream
, ifstream
, ofstream
, for example, provide unique ownership through move operations and deleted or prohibited copy operations.
You are defining a copyable resource managing class (aka General Manager) type that requires you define a destructor.
When a destructor is defined, such a class type will no move operations, and defaulted copy-operations that do the wrong thing.
The following forces make defining the special member functions for a copyable manager class hard:
Define the following special member functions:
noexcept
guarantee and can be more efficient than copy.noexcept
guarantee and can be more efficient than copy.=default
creating an object in an empty moved-from state.swap
and namespace-level swap
functions, if std::swap
would be less efficient.The standard library container class templates and string classes support value semantics, while managing the memory (resource) for storing their elements. The container’s destructors can be elaborate, because prior to releasing their memory, which could be automatic by using a unique_ptr
, for example, they need to destroy the held elements individually to end their lifetime before the memory they occupy gets released.
Scott Meyers taught us to always define a destructor, copy constructor, and copy assignment-operator together, when one of them is needed.
You are stuck with C++03 and have a class where you need to define a destructor and thus can not apply Rule of DesDeMovA. Or, the compiler-provided copy operations do not guarantee the invariant of your class. Usually in modern code you will use the Rule of Five or the Rule of DesDeMovA instead.
When a destructor is defined, this often means either a resource is managed, or copying can lead to slicing in case of the destructor being virtual.
TODO The following forces make defining such a class’ special member functions hard:
Declare the following special member functions:
=0;
) if the base class is abstract.private:
; otherwise define it public:
Exceptions to the rule above exist in older code, even of the standard library that defines copy operations as protected:
to enable concrete subclasses to implement them, when possible. However, this is very convoluted design that you should not employ in your object-oriented dynamic polymorphic class hierarchies. Correctly dealing with it in subclasses requires high discipline and understanding of the design that only libraries on the level of scrutiny of the standard library can ensure.
Please note that the details of that table will be discussed elsewhere.↩︎