Article

Read

Rule of three (C++)

from Wikipedia, the free encyclopedia

In the C++ programming language, the rule of three (pre-C++11) or rule of five (C++11 and later) refers to a recommendation that in a class that defines one of the following three or five element functions, the other two or four should also be defined:[1]

  1. Destructor
  2. Copy constructor
  3. Copy assignment operator
  4. Move constructor
  5. Move assignment operator

The rule is a mandatory part of common conventions such as C++ Core Guidelines[2] or AUTOSAR[3].

Background

The above mentioned element functions are normally generated automatically by the compiler. These generated versions have a meaning defined in the language standard: All non-static data elements are copied (2 and 3) or moved (4 and 5) or released/cleared (1) in the reverse order of their declaration.

However, if a class has different semantics, for example because it contains a resource as a data element that cannot be copied, moved, or cleared in this way, each of these element functions can be replaced by its own definition. In most cases, such classes then require that all three or five of these element functions have their own custom implementation.

Examples

Resources about handles

If a data element represents a resource (e.g. a file, TCP or database connection) via a handle, the compiler usually does not know this meaning. The data element is e.g. of type int. In order to close or release the represented resource in the destructor of the class, the class must have a user-defined destructor in which the resource is explicitly closed/released via a system call.

Similarly, the copy constructor and assignment operator must do more than just copy the handle so that the original object and copy can access the resource without conflict. If the resource cannot or should not be accessed from multiple objects, the two element functions must be explicitly deleted, which means that an object of this type cannot be copied:

class File
{
public:
    File(const char* filename)
    : file(fopen(filename, "rb"))
    { /* Error handling, etc. */ }

    // Rule of Three:
    File(const File&) = delete; // No copy!
    ~File() { fclose(file); }
    void operator=(const File&) = delete; // No copy!

    // further element functions
    // ...

private:
    FILE* file;
};

Resource about “naked” pointers

Similarly, if a resource is referenced via a “naked pointer”. The compiler-generated functions copy the pointer, but not the resource referenced over it. This is also called a flat copy. As a result, the source object and the copy share the resource. Furthermore, it is not clear which of the objects “owns” the shared resource, i.e. who is responsible for clearing the resource.

So the class needs user-defined definitions for the copy constructor and the assignment operator. In these, either the referenced resource must then be explicitly duplicated(deep copy) or access must be regulated in some other way; if necessary, the only sensible solution here is also to prohibit the copying of objects of this class altogether by explicitly deleting these element functions.

Modern compilers provide the ability to issue a warning when a class is defined that contains “naked pointers” as data elements, but does not satisfy the rule of three.

Another possibility is the use of smart pointers, which encapsulate the referenced resource in a defined way, while also clearly regulating the access and lifetime of a possibly shared resource. The C++ standard library has provided its own smart pointer classes for this purpose since C++11:

Smart Pointer Class Meaning
std::unique_ptr<T> Resource cannot be copied implicitly. The compiler issues an error if an attempt is made to copy an object of a class that has unique_ptr data elements but no user-defined copy functions.
std::shared_ptr<T> The referenced resource is shared by the original and target objects. The number of copies is noted via a counter so that the resource can be cleared/released when the lifetime of the last copy ends.
std::weak_ptr<T>

unique_ptr and shared_ptr also contain an optional “deleter” function, in case the referenced resource cannot simply be released via delete.

Since C++11

Since the release of C++11, the rule of three with the two functions

  • Move constructor
  • Move assignment operator

extended to the rule of five.[4][5]

On the other hand, the responsibilities of the classes should be separated:

  1. Classes that hold exactly one resource each. The rule of three or five then generally applies to these, but you only need a few of these resource management classes. Also, the existing smart pointers of the standard library can often be used for this.
  2. Classes that merely aggregate other resources in their data members. These classes then do not need user-defined copy functions or destructors, since the compiler-generated functions then automatically contain the correct semantics. This greatly simplifies the implementation and testing of these classes.

This approach is also called rule of zero, since the vast majority of classes belong to the second category without user-defined copy functions and destructors.[6]

Since C++11 it is also possible not only to explicitly suppress the generation of the compiler-generated version, but also to explicitly force it(=default). This tells the compiler (and also the human reader) that in this case the compiler-generated version provides exactly the desired behavior, so that you do not have to implement it manually:

class Example
{
    Example(const Example&) = default;  // force compiler generated version
    void operator=(const Example&) = delete; // prevent compiler generated version
};

Literature

  • Stanley B. Lippman, Josèe Lajoie, Barbara E. Moo: C++ Primer. 4. Edition. Addison-Wesley Professional, 2005, ISBN 0-201-72148-1.

Individual references

  1. Bjarne Stroustrup: The C++ Programming Language. 3. Edition. Addison-Wesley, 2000, ISBN 0-201-70073-5, pp. 283-284.
  2. C.21: If you define or =delete any copy, move, or destructor function, define or =delete them all, C++ Core Guidelines, August 3, 2020, retrieved September 8, 2020.
  3. Rule A12-0-1, Chapter 6.12.0, Guidelines for the use of the C++14 language in critical and safety-related systems, AUTOSAR AP Release 2019-03, p. 201.
  4. Proposing the Rule of Five (PDF)
  5. Proposing the Rule of Five, v2 (PDF)
  6. The rule of three/five/zero, cppreference.com, retrieved 8 September 2020.