Published on

Rule of 3 and 5 in C++

Authors

Rule of 3 and 5

In modern C++, the special member functions include the default constructor, destructor, copy constructor, copy assignment, move constructor, and move assignment.

These functions are only generated by the compiler if they are needed, and they perform member-wise copy or moves. When present, the move constructor and move assignment also handle base class parts accordingly.

It's important to note that defining a move constructor doesn't guarantee that member variables will be move-constructed, especially if those variables do not support move construction. In such cases, the default behavior reverts to copy construction.

Rules regarding copy and move functions

Copy construction and copy assignment operate independently. If you declare one but use the other elsewhere in your code, the compiler will automatically generate the definition for the undeclared function.

However, move functions function differently. Declaring one prevents the compiler from generating the other. The rationale is that a custom move constructor signifies a specific implementation need, and the compiler refrains from assuming otherwise.

Expanding on this, if an explicit copy constructor is declared, move operations won't be generated automatically for the same reasons mentioned above.

Conversely, if move operations are explicitly declared, copy operations will be deleted.

Furthermore, any class with a destructor doesn't generate move operations automatically, although it does generate copy operations due to legacy reasons.

Rule of three

The rule of three advises that if you define a copy constructor, copy assignment, or destructor, it's best to define all three. This guideline arises from the likelihood of handling resources in a specific manner, and it's preferable for the compiler not to make assumptions.

So, move operations are only generated if three conditions hold true:

  1. No copy operations are declared in the class.
  2. No move operations are declared in the class.
  3. No destructor is declared in the class.

C++11 deprecates the automatic generation of copy operations for classes declaring either copy operations or a destructor. To avoid any warnings, consider using default as follows:

class GUI {
  public:
  ~GUI(); // User declared
  GUI(const GUI&) = default; // Default
  GUI& operator=(const GUI&) = default; // Default
};

This principle extends to move operations as well:

class GUI {
  public:
  ~GUI(); // User declared
  GUI(const GUI&) = default; // Support copy
  GUI& operator=(const GUI&) = default;

  GUI(GUI&&) = default; // Support move
  GUI& operator=(GUI&&) = default;
};

These rules aim to mitigate subtle bugs. For instance, if a destructor and copy constructor are defined and then an attempt is made to move an object, the code will compile and run. However, the move won't occur because the destructor has deleted the move constructor, creating the illusion of a move operation. To prevent such issues, it's advisable to define the move constructor as default, leading to the rule of 5: define destructor, copy constructor, copy assignment, move constructor, and move assignment.

Things to remember

  1. Move operations are generated automatically only if destructor, copy operations, and move operations are not explicitly defined.
  2. Copy constructor and copy assignment are generated if the class lacks them, and they are deleted if a move operation is declared. Similarly, the generation of copy operations in a class with a destructor is deprecated.