The Rule of Zero

Published:

Content

Introduction

Modern C++ code should use the Resource Acquisition Is Initialisation (RAII) / Resource Release Is Destruction (RRID) idiom. Bjarne Stroustrop, the inventor of the C++ language describes RAII as follows:

The basic idea [of RAII] is to represent a resource by a local object, so that the local object’s destructor will release the resource. That way, the programmer cannot forget to release the resource.

Source: Bjarne Stroustrup’s C++ Style and Technique FAQ

RAII is also described in detail as Item 13: Use objects to manage resources in Scott Meyers book “Effective C++”. Mr. Meyers suggests the following:

To prevent resource leaks, use RAII objects that acquire resources in their constructors and release them in their destructors.

Source: Meyers, Scott. Effective C++, 3rd Edition, p. 66.

Resource Management Types

In modern C++, the following four types of objects from a resource management perspective can be categorized:

  • Copyable, but not moveable.
  • Both copyable and moveable.
  • Moveable, but not copyable.
  • Neither copyable nor moveable.

The following matrix illustrates the four cases in a more compact form.

  Copyable Uncopyable
Moveable {C, M} {UC, M}
Unmoveable {C, UM} {UC, UM}

To define the behavior of a class regarding copy and move semantics, modern C++ offers the following constructs:

The following rules can be applied for the resource management types:

  • Uncopyable (UC)
    • Declare the copy constructor as deleted.

      Example: MyObject(MyObject const& kSource) = delete;

    • Declare the assignment operator as deleted.

      Example: MyObject& operator=(MyObject const& kRhs) = delete;

  • Unmoveable (UM)
    • Declare the move constructor as deleted.

      Example: MyObject(MyObject&& source) = delete;

    • Declare the move operator as deleted.

      Example: MyObject& operator=(MyObject&& rhs) = delete;

  • Copyable (C)
    • If using automatic resource management: Apply The Rule of Zero.
    • If using manual resource management: Apply The Rule of Three.
  • Moveable (M)
    • If using automatic resource management: Apply The Rule of Zero.
    • If using manual resource management: Apply The Rule of Five.

Instead of using delete the mentioned functions can be declared with private visibility. This allows to implement uncopyable objects if using an older standard, such as C++98.

The Rule of […]

A short description with source code examples of the The Rule of Three, The Rule of Five and The Rule of Zero can be found at cppreference.com. This Stack Overflow question also deals with the three rules.

I will summarize each rule in this article. Please refer to the linked information resources for more in-depth information.

The Rule of Three

The term The Rule of Three was coined by Marshall Cline in 1991.

I recommend reading the article The Rule of The Big Three (and a half) – Resource Management in C++ by Glennan Carnie.

The Rule of Three allows to implement C and UC.

tl;dr: If a class manually manages a resource (e. g. with a raw pointer), the destructor, copy assignment operator and copy constructor have to be implemented. If either one of them is implemented, the other two should also be implemented.

Example:

The Rule of Five

I recommend reading the article The Rule of The Big Four (and a half) – Move Semantics and Resource Management by Glennan Carnie.

The Rule of Five allows to implement {C, M}, {UC, M}, {C, UM} and {UC, UM}.

tl;dr: If a class has a user-defined destructor, user-defined copy constructor or user defined copy assignment operator, the move constructor and the move assignment operator have to be also be implemented to realize move semantics.

Example:

The Rule of Zero

The Rule of Zero was introduced by R. Martinho Fernandes on 15 August 2012. It is defined as follows:

Classes that have custom destructors, copy/move constructors or copy/move assignment operators should deal exclusively with ownership. Other classes should not have custom destructors, copy/move constructors or copy/move assignment operators.

Scott Meyers corrects the definition in his blog post A Concern about the Rule of Zero as follows:

Classes that declare custom destructors, copy/move constructors or copy/move assignment operators should deal exclusively with ownership. Other classes should not declare custom destructors, copy/move constructors or copy/move assignment operators.

This basically means that one should never use a raw pointer to manage a resource. Therefore no destructor, copy constructor, copy assignment operator, move constructor and move assignment operator has to be implemented.

The Rule of Zero allows to implement {C, M}, {UC, M}, {C, UM} and {UC, UM}, without declaring them explicitly. The emphasized part of the last sentence is the important difference to The Rule of Five.

The aim of The Rule of Zero is to eliminate resource management for the user and let the Standard Library do all the work related to resource management.

The latest approved ISO C++ Standard C++14 describes The Rule of Zero in the section 12.8 (“Copying and moving class objects”):

[12.8/8:] If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator, and
  • X does not have a user-declared destructor.

[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise would have invoked the move constructor may instead invoke a copy constructor. —end note ]

[12.8/20:] If the definition of a class X does not explicitly declare a move assignment operator, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared move constructor,
  • X does not have a user-declared copy assignment operator, and
  • X does not have a user-declared destructor.

More in-depth information about The Rule of Zero can be found in the following web links:

Suggestions

  • Use Smart Pointers instead of raw pointers:
    • std::unique_ptr if an instance of a class can be moved, but not copied and does not have to be shared.
    • std::shared_ptr if an instance of a class can be copied and has to be shared.
  • Avoid C-style language constructs (especially as class member attributes):
    • The string class std::basic_string instead of a C-string (char*).
    • The container class std::array instead of C-arrays (e. g. std::uint16_t my_var[10];).

Example

The Rule of the Five defaults

There exists one concern with the Rule of Zero, as described in A Concern about the Rule of Zero:

The addition of the destructor has the side effect of disabling generation of the move functions […].

Mr. Meyers suggests using default to enforce The Rule of Zero:

[I]nstead of expressing [The Rule of Zero] by not declaring [member functions for copying, moving and destruction] functions, [the rule is] expressed by declaring them explicitly and equally explicitly opting in to the compiler-generated implementations.

Depending on the compiler used, The Rule of Five defaults should be used as pointed out in the comments of the blog post A Concern about the Rule of Zero. Older compiler versions may not raise a warning if using the deprecated (since C++11) implicit generation of copies if there is a destructor present.

Example:

Summary

In my opinion, The Rule of Zero is currently the best practice for resource management if developing in the C++ language. It should be considered as the “modern” way, and all other approaches using manual resource management should be seen as “legacy” C-style language relicts.

By using automatic resource management, all required constructors and assignment operators can be implicitly declared and defined by the compiler.

tl;dr: Always use automatic resource management in C++ by enforcing The Rule of Zero. Only use manual resource management (The Rule of Three, The Rule of Five) if the environment enforces it, e. g. due to a requirement.

Ressources