Welcome

The latest news:

Introducing the Rule of DesDeMovA
01 Jul 2019

This is my first Blog post on Safe C++.

I am writing a book on “Modern C++ for Safety Critical Systems”. While having many ideas, that I often present in my conference talks, those might not be as easily accessible. Therefore, I followed the lead of others to write down some of these ideas in a blog, to make transparent the ideas collected for the book and to allow others help me rid my writing of mistakes and unsound opinions and conclusions.

Please be patient but give feedback and note, that the technical content of the blog pages might change, as the book content evolves.

Peter.

To start with, let me present the following draft of:

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:

  • Defining any constructor, such as the copy constructor, denies the ability of the default constructor being automatically provided by the compiler.
  • Defining move operations requires them to be noexcept, otherwise they can not be used for optimization when objects of this type is put into standard containers.
  • User-implemented copy and move operations must deal with the presence of base classes explicitly and need to be adjusted when evolution adds member variables. The compiler provided versions will be automatically correct, unless your class is managing a resource directly.
  • User-defined special member functions can make the class type lose “trivial” or “literal” properties, resulting in potentially less-efficient code.
  • Defining correct special member functions with exactly the same semantics and efficiency as the compiler-provided ones can be tricky, because the compiler can employ more internal knowledge for optimization.

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:

  • Code that is not written can not be wrong.
  • Compiler-provided special member functions will have best possible efficiency.

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

  • You might be confused by the impact of member variable types on the provision of special member functions, e.g., a reference member disables copy and move operations. Consult the table on how member variables influence the compiler-provided special member functions.

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:

  • Code that is not written can not be wrong, so you want to write the minimal set of code to achieve a goal.
  • Defining any constructor, even defining the copy constructor as deleted, prevents the compiler from providing the default constructor.
  • Defining special member functions with exactly the same semantics and efficiency as the compiler-provided ones can be hard.

Solution

Define the move-assignment operator as deleted.

Consequences

  • Code that is not written can not be wrong.
  • Compiler-provided special member functions will have best possible efficiency.
  • You may be confused by the impact of member variable types on the provisioning of special member functions, e.g., a reference member disables copy and move operations.

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:

  • Your resource managing class manages a resource who’s duplication would lead to erroneous behavior such as an operating system resource handle.
  • Your resource managing class should be returned from (factory) functions.
  • Your resource managing class should have a noexcept move and swap operations to avoid leaking the resource.

Solution

Define the following special member functions:

  • the move-constructor that transfers ownership. This requires a moved-from state that is detectable in the destructor, to avoid double release of the resource.
  • the move-assignment operator that transfers ownership.
  • the default constructor as =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.
  • a member 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);
}

Consequences

  • You have a class for unique resources that is move only.
  • Your class move operations transfer the ownership of the held resource thus effectively preventing double release of the resource
  • Defining the move operations automatically disables copying.
  • Defining a moved-from state when there is uniqueness requirement can introduce space overhead if all values of the type of the held resource are possibly valid
  • Defining move operations correctly often involves expert-level experience
  • Users of your class must be aware that it is move-only, so when they pass a variable to transfer ownership they need to wrap it in std::move()

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:

  • Your resource managing class manages a copyable resource such as a collection of copyable values.
  • Your resource managing class should support value semantics, even if that has overhead in allocation and copying.
  • Your resource managing class should support efficient move operations, this requires a sane moved-from state.
  • Your resource managing class should provide the strongest exception guarantee possible during move and copy operations.

Solution

Define the following special member functions:

  • the copy-constructor
  • the copy-assignment operator
  • the move-constructor, if move can provide noexcept guarantee and can be more efficient than copy.
  • the move-assignment, if move can provide noexcept guarantee and can be more efficient than copy.
  • the default constructor as =default creating an object in an empty moved-from state.
  • a member swap and namespace-level swap functions, if std::swap would be less efficient.

Consequences

  • you have a magnificent class (TODO)
  • Defining move operations correctly often involves expert-level coding.
  • Defining copy operations with the strong exception guarantee requires expert-level experience, or the copy-swap idiom
  • Defining a correct destructor for a value-semantics class that owns its resources can be complicated, because it needs to avoid throwing an exception, correctly ending the lifetime of all managed objects and releasing all managed resources, such as memory.

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:

  • Your resource managing class manages a copyable resource such as a collection of copyable values.
  • Your resource managing class should support value semantics, even if that has overhead in allocation and copying.
  • Your resource managing class should support efficient move operations, this requires a sane moved-from state.
  • Your resource managing class should provide the strongest exception guarantee possible during move and copy operations.

Solution

Declare the following special member functions:

  • Define a destructor. If in an object-oriented hierarchy, declare it virtual and define it as pure (=0;) if the base class is abstract.
  • Declare a copy-constructor, if the destructor is virtual or your class can not be sanely copied, declare it as private:; otherwise define it public:
  • Declare a copy-assignment operator according to the same rules as given above.

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

  • you have a something (TODO)
  • In modern code the Rule of Three can lead to pessimization and is less clear than Rule of Zero or Rule of DesDeMovA.
  • Defining copy operations with the strong exception guarantee requires expert-level experience, or the copy-swap idiom.

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