In a previous article – ”The Rule of the Big Four (and a half)” we looked at resource management policies in C++.
Resource management is the general term for using the mechanisms in C++ to ensure that resources – files, dynamic memory, sockets, mutexes, etc – have their lifetimes automatically controlled so as to prevent resource leaks, deadlocks, etc. C++ refers to these mechanisms as RAII/RDID ( “Resource Acquisition Is Initialisation / Resource Destruction is Deletion”)
In this article we’ll have a look at a complementary guideline to help simplify your application code, without risking resource management issues – The Rule of Zero.
The term The Rule of Zero was coined by R. Martinho Fernandes in his 2012 paper (http://flamingdangerzone.com/cxx11/2012/08/15/rule-of-zero.html). This article merely reflects Martinho Fernandes’ work and I highly recommend reading the original paper to get the full details of the concepts.
If you’re not already familiar with the concepts of resource management I’d suggest having a look at the previous articles – The Rule of Three (and a half) and The Rule of Four (and a half) before reading on.
The four categories of resource manager
From a resource management perspective we can categorise types in four ways:
- Objects that can be both moved and copied
- Objects for which it makes sense to copy but not move
- Objects for which it makes sense to move but not copy
- Objects which should neither be moved not copied.
The Rule of The Big four (and a half) is a guideline for implementing the copy/move policies for the types in your system. Essentially, it states:
- If you have written a (non-default) destructor for a class you should implement the copy constructor and assignment operator for the class; or mark them as deleted.
- Similarly, if you have written either a (non-default) copy constructor or assignment operator for the class you must write a destructor.
- If your class can be moved you must implement both the move constructor and move assignment operator; or mark them as deleted.
With copy policy it is often performance issues – memory, speed, efficiency – that determine whether it is ‘sensible’ to copy a class. For example, it is possibly unwise to copy a 1MByte data file being owned as a resource!
In other cases the decision may be made based on whether replicating the owned resource may have detrimental consequences – for example, replicating an OS mutex could cause potential difficult-to-identify race conditions in code.
Moving a resource is commonly considered an efficiency optimisation for copying. However, the moved-from object must be left in an ‘empty’ state. What ‘defines’ empty (obviously) varies from object to object but a good rule-of-thumb is: If the class does not have a default constructor then it probably shouldn’t support move semantics.
The Rule of Zero
An alternative to “The Rule of The Big Four (and a half)” has appeared in the form of “The Rule of Zero”. “The Rule of Zero” basically states:
You should NEVER implement a destructor, copy constructor, move constructor or assignment operators in your code.
With the (very important) corollary to this:
You should NEVER use a raw pointer to manage a resource.
The aim of The Rule of Zero is to simplify your application code by deferring all resource management to Standard Library constructs, and letting them do all the hard work for you.
The Rule of Zero and dynamic memory
If your code must dynamically create objects prefer to use std::unique_ptr or std::shared_ptr. Use a std::unique_ptr if your class can be moved, but should not be copied:
Use a shared_ptr if you need to support copying as well as moving:
This code works because the compiler generates default implementations of the copy constructor, move constructor and assignment operators for your class. These default implementations will invoke the appropriate functions on the shared_ptr / unique_ptr (if available). The C++ Standard limits when these constructors are created, as follows:
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, and
- X does not have a user-declared copy assignment operator,
- X does not have a user-declared move assignment operator,
- X does not have a user-declared destructor, and
- The move constructor would not be implicitly defined as deleted.
(Section 12.8/22 specifies a very similar rule for assignment operators)
In other words, The Rule of Zero!
The Rule of Zero and strings
In the case of strings The Rule of Zero says prefer to use std::string over arrays of characters – particularly dynamically allocated (that is, variable-sized) arrays of characters.
Notice std::string supports both copying and move semantics; and after move leaves the string object as ‘empty’ (in this case, the null string).
The Rule of Zero and containers
The only built-in container in C++ is the array. This is sometime referred to as a ‘degenerate’ container because it is merely syntactic sugar coating pointer arithmetic.
Array notation basically hides the fact (the problem!) that arrays are little more than (raw) pointers. Because of this they are easily abused – either deliberately or accidently. Therefore, The Rule of Zero’s corollary still applies when it comes to arrays: don’t use them. This becomes particularly relevant when the arrays are created dynamically (with new).
Instead, The Rule of Zero prefers that we use Standard Library container classes.
If variable-sized containers aren’t required, prefer std::array to a built-in array. When a std::array is moved it calls the move assignment operator on each of its elements (as with all other objects, if the element in the array doesn’t support move it is copied instead)
The Rule of Zero is a guideline for simplifying application code, whilst avoiding the major problems associated with resource management in C++ programs.
The Rule of Zero is the ‘modern’ way to write C++ and there is very little good reason to fall back on C-style manual resource management. Experience and evidence has shown us that we, as developers, are just not particularly adept at it.
If you’d like to know more about topics like this have a look at our C++ training courses:
Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.
He specialises in C++, UML, software modelling, Systems Engineering and process development.