florianwolters The Rule of Zero
NOTE:
一、是通过 feabhas 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).
显然,是本文首次提出了rule of zero
二、本文是从automatic resource management 和 manual resource management 的角度来说明rule of three/five/zero,在"Resource Management Types"章节中进行了非常好的总结。
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:
1、Copyable, but not moveable.
2、Both copyable and moveable.
3、Moveable, but not copyable.
4、Neither copyable nor moveable.
NOTE: copyable和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)
1、Declare the copy constructor as deleted.
Example:
MyObject(MyObject const& kSource) = delete;
2、Declare the assignment operator as deleted.
Example:
MyObject& operator=(MyObject const& kRhs) = delete;
Unmoveable (UM)
1、Declare the move constructor as deleted.
Example:
MyObject(MyObject&& source) = delete;
2、Declare the move operator as deleted.
MyObject& operator=(MyObject&& rhs) = delete;
Copyable (C)
1、If using automatic resource management: Apply The Rule of Zero.
2、If using manual resource management: Apply The Rule of Three.
Moveable (M)
1、If using automatic resource management: Apply The Rule of Zero.
2、If using manual resource management: Apply The Rule of Five.
Instead of using delete
, the mentioned functions can be declared withprivate
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
/**
* Demonstrates *The Rule of Three* C++ idiom.
*
* @file the_rule_of_three.h
* @author Florian Wolters <wolters.fl@gmail.com>
*
* @section License
*
* Copyright Florian Wolters 2015 (http://blog.florianwolters.de).
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef FW_IDIOM_THE_RULE_OF_THREE_H_
#define FW_IDIOM_THE_RULE_OF_THREE_H_
#include <cstring>
#include <utility>
namespace fw
{
namespace idiom
{
/**
* The TheRuleOfThree class demonstrates *The Rule of Three* idiom.
*
* @author Florian Wolters <wolters.fl@gmail.com>
*/
class TheRuleOfThree final
{
public:
/**
* Initializes a new instance of the TheRuleOfThree class with the specified
* C-style string.
*
* @param kValue The C-style string.
*/
explicit TheRuleOfThree(char const *kValue) :
resource_ { new char[std::strlen(kValue) + 1] }
{
std::strcpy(resource_, kValue);
}
/**
* Finalizes an instance of the TheRuleOfThree class.
*
* This is the destructor.
*/
~TheRuleOfThree()
{
delete[] resource_;
}
/**
* Initializes a new instance of the TheRuleOfThree class from the specified
* TheRuleOfThree.
*
* This is the copy constructor.
*
* @param kValue The TheRuleOfThree to copy.
*/
TheRuleOfThree(TheRuleOfThree const &kOther) :
resource_ { new char[std::strlen(kOther.resource_) + 1] }
{
std::strcpy(resource_, kOther.resource_);
}
/**
* Assigns the specified TheRuleOfThree to this TheRuleOfThree.
*
* This is the copy assignment operator.
*
* @param kValue The TheRuleOfThree to assign to this
* TheRuleOfThree.
*/
TheRuleOfThree& operator=(TheRuleOfThree &kOther)
{
std::swap(resource_, kOther.resource_);
return *this;
}
private:
/**
* The resource (a raw pointer to a character) to handle by this class.
*/
char *resource_;
};
} // namespace idiom
} // namespace fw
#endif // FW_IDIOM_THE_RULE_OF_THREE_H_
int main()
{
using fw::idiom::TheRuleOfThree;
// Complete constructor.
TheRuleOfThree the_rule_of_three { "hello, world" };
// Copy constructor.
TheRuleOfThree copy { the_rule_of_three };
// Copy assignment operator.
copy = the_rule_of_three;
// Destructor(s).
}
// g++ test.cpp --std=c++11 -pedantic -Wall -Wextra
NOTE:
一、上述code中的copy assignment的实现是比较奇怪的,我觉得是有问题的: 它仅仅执行了swap,这是违反了assignment operator的语义的;正确的写法是"Assignment operator-pass by value-copy and swap idiom-strong exception safety",参见:
1、feabhas The Rule of The Big Three (and a half) – Resource Management in C++
the_rule_of_three.h hosted with ❤ by GitHub
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}.
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:
/**
* Demonstrates *The Rule of Five* C++ idiom.
*
* @file the_rule_of_five.h
* @author Florian Wolters <wolters.fl@gmail.com>
*
* @section License
*
* Copyright Florian Wolters 2015 (http://blog.florianwolters.de).
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef FW_IDIOM_THE_RULE_OF_FIVE_H_
#define FW_IDIOM_THE_RULE_OF_FIVE_H_
#include <cstring>
#include <utility>
namespace fw
{
namespace idiom
{
/**
* The TheRuleOfFive class demonstrates *The Rule of Five* idiom.
*
* @author Florian Wolters <wolters.fl@gmail.com>
*/
class TheRuleOfFive final
{
public:
/**
* Initializes a new instance of the TheRuleOfFive class with the specified
* C-style string.
*
* @param kValue The C-style string.
*/
explicit TheRuleOfFive(char const *kValue) :
resource_ { new char[std::strlen(kValue) + 1] }
{
std::strcpy(resource_, kValue);
}
/**
* Finalizes an instance of the TheRuleOfFive class.
*
* This is the destructor.
*/
~TheRuleOfFive()
{
delete[] resource_;
}
/**
* Initializes a new instance of the TheRuleOfFive class from the specified
* TheRuleOfFive.
*
* This is the copy constructor.
*
* @param kValue The TheRuleOfFive to copy.
*/
TheRuleOfFive(TheRuleOfFive const &kOther) :
resource_ { new char[std::strlen(kOther.resource_) + 1] }
{
std::strcpy(resource_, kOther.resource_);
}
/**
* Assigns the specified TheRuleOfFive to this TheRuleOfFive.
*
* This is the copy assignment operator.
*
* @param kValue The TheRuleOfFive to assign to this TheRuleOfFive.
*/
TheRuleOfFive& operator=(TheRuleOfFive &kOther)
{
std::swap(resource_, kOther.resource_);
return *this;
}
/**
* Initializes a new instance of the TheRuleOfFive class from the specified
* TheRuleOfFive.
*
* This is the move constructor.
*
* @param kValue The TheRuleOfFive to copy.
*/
TheRuleOfFive(TheRuleOfFive &&other) :
resource_ { other.resource_ }
{
other.resource_ = nullptr;
}
/**
* Assigns the specified TheRuleOfFive to this TheRuleOfFive.
*
* This is the move assignment operator.
*
* @param kValue The TheRuleOfFive to assign to this TheRuleOfFive.
*/
TheRuleOfFive& operator=(TheRuleOfFive &&other)
{
std::swap(resource_, other.resource_);
return *this;
}
private:
/**
* The resource (a raw pointer to a character) to handle by this class.
*/
char *resource_;
};
} // namespace idiom
} // namespace fw
#endif // FW_IDIOM_THE_RULE_OF_FIVE_H_
/**
* Runs the application.
*
* @return Always `0`.
*/
int main()
{
using fw::idiom::TheRuleOfFive;
// Complete constructor.
TheRuleOfFive the_rule_of_five_defaults { "hello, world" };
// Copy constructor.
TheRuleOfFive copy { the_rule_of_five_defaults };
// Move constructor.
TheRuleOfFive move { std::move(copy) };
// Copy assignment operator.
copy = the_rule_of_five_defaults;
// Move assignment operator (from rvalue).
move = TheRuleOfFive { "foo" };
// Move assignment operator (from lvalue).
move = std::move(copy);
// Destructor(s).
}
// g++ test.cpp --std=c++11 -pedantic -Wall -Wextra
NOTE:
一、上述code,同样存在一些问题,参见 feabhas The Rule of The Big Four (and a half) – Move Semantics and Resource Management ,其中给出了更好的实现方式
the_rule_of_five.h hosted with ❤ by GitHub
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.
NOTE:
上面的总结是不够直观明了的,在 feabhas 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 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”):
Suggestions
Use Smart Pointers instead of raw pointers:
if an instance of a class can be moved, but not copied and does not have to be shared.
if an instance of a class can be copied and has to be shared.
Avoid C-style language constructs (especially as class member attributes):
1、The string classstd::basic_string
instead of a C-string (char*
).
2、The container classstd::array
instead of C-arrays (e. g.std::uint16_t my_var[10];
).
/**
* Demonstrates *The Rule of Zero* C++ idiom.
*
* @file the_rule_of_zero.h
* @author Florian Wolters <wolters.fl@gmail.com>
*
* @section License
*
* Copyright Florian Wolters 2015 (http://blog.florianwolters.de).
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef FW_IDIOM_THE_RULE_OF_ZERO_H_
#define FW_IDIOM_THE_RULE_OF_ZERO_H_
#include <string>
#include <utility>
namespace fw
{
namespace idiom
{
/**
* The TheRuleOfZero class demonstrates *The Rule of Zero* idiom.
*
* @author Florian Wolters <wolters.fl@gmail.com>
*/
class TheRuleOfZero final
{
public:
/**
* Initializes a new instance of the TheRuleOfZero class with the specified
* string.
*
* @param kValue Thestring.
*/
explicit TheRuleOfZero(std::string const &kValue) :
value_ { kValue }
{
// NOOP
}
private:
/**
* The value of this TheRuleOfZero instance.
*/
std::string value_;
};
} // namespace idiom
} // namespace fw
#endif // FW_IDIOM_THE_RULE_OF_ZERO_H_
/**
* Runs the application.
*
* @return Always `0`.
*/
int main()
{
using fw::idiom::TheRuleOfZero;
// Complete constructor.
TheRuleOfZero the_rule_of_five_defaults { "hello, world" };
// Copy constructor.
TheRuleOfZero copy { the_rule_of_five_defaults };
// Move constructor.
TheRuleOfZero move { std::move(copy) };
// Copy assignment operator.
copy = the_rule_of_five_defaults;
// Move assignment operator (from rvalue).
move = TheRuleOfZero { "foo" };
// Move assignment operator (from lvalue).
move = std::move(copy);
// Destructor(s).
}
// g++ test.cpp --std=c++11 -pedantic -Wall -Wextra
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.
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.