Rules for Special Member Functions

This section is based on the seminal table by Howard Hinnant that shows the rules for special member functions introduced with C++11.1

Howard Hinnants ACCU 2014 table (redrawn)

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
struct X {
    virtual ~X(){}
  private:
    X(X const &);
    X const& operator=(X const &);
};
struct X {
    virtual ~X() = default;
    X(X const &) = delete;
    X const& operator=(X const &) = delete;
};

Rule of Zero

Code that you do not write can not be wrong

Context

You are defining a class type and need to consider the definition of the class’ special member functions.

Problem

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.

Forces

The following forces make defining a class’ special member functions hard:

Solution

Select member variable types, so that the compiler provided copy and move operations and destructor are correct.

Consequences

Applying the Rule of Zero provides you with the following benefits:

You have to consider the following liabilities from applying the Rule of Zero:

Rule of DesDeMovA

Desdemona is a tragic character in Shakespeare’s Othello. Don’t let your class be the tragic character in your code base.

Context

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).

Problem

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.

Forces

The following forces make defining the special member functions for a class hard:

Solution

Define the move-assignment operator as deleted.

Consequences

Rule of Move Only

aka Rule of Unique.

Unique ownership of a resource is one of the key contributions of the Move Semantics introduced with C++11

Context

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.

Problem

When a destructor is defined, such a class type will have no move operations, and defaulted copy-operations that do the wrong thing.

Forces

The following forces make defining the special member functions for a unique manager class hard:

Solution

Define the following special member functions:

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);
}

Consequences

Known Uses

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.

Rule of Five

Context

You are defining a copyable resource managing class (aka General Manager) type that requires you define a destructor.

Problem

When a destructor is defined, such a class type will no move operations, and defaulted copy-operations that do the wrong thing.

Forces

The following forces make defining the special member functions for a copyable manager class hard:

Solution

Define the following special member functions:

Consequences

Known Uses

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.

Rule of Three

Scott Meyers taught us to always define a destructor, copy constructor, and copy assignment-operator together, when one of them is needed.

Context

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.

Problem

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.

Forces

TODO The following forces make defining such a class’ special member functions hard:

Solution

Declare the following special member functions:

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.

Consequences


  1. Please note that the details of that table will be discussed elsewhere.↩︎