Rule-of-Three becomes Rule-of-Five with C++11?
所以,在看了这个关于右值引用的精彩演讲之后,我认为每个类都会受益于这样一个"移动构造函数",
我认为三的规则变成三、四和五的规则:
Each class should explicitly define exactly one
of the following set of special member
functions:
- None
- Destructor, copy constructor, copy assignment operator
In addition, each class that explicitly defines a destructor may explicitly define a move constructor and/or a move assignment operator.
Usually, one of the following sets of special member
functions is sensible:
- None (for many simple classes where the implicitly generated special member functions are correct and fast)
- Destructor, copy constructor, copy assignment operator (in this case the
class will not be movable)- Destructor, move constructor, move assignment operator (in this case the class will not be copyable, useful for resource-managing classes where the underlying resource is not copyable)
- Destructor, copy constructor, copy assignment operator, move constructor (because of copy elision, there is no overhead if the copy assignment operator takes its argument by value)
- Destructor, copy constructor, copy assignment operator, move constructor,
move assignment operator
注意,对于显式声明任何其他特殊成员函数的类,将不会生成移动构造函数和移动赋值运算符,对于显式声明移动构造函数或移动赋值运算符的类和显式DECL类,将不会生成复制构造函数和复制赋值运算符。AES析构函数和隐式定义的复制构造函数或隐式定义的拷贝赋值运算符被认为是不成立的。特别是以下完全有效的C++ 03多态基类
1 2 3 | class C { virtual ~C() { } // allow subtype polymorphism }; |
应改写如下:
1 2 3 4 5 6 7 | class C { C(const C&) = default; // Copy constructor C(C&&) = default; // Move constructor C& operator=(const C&) = default; // Copy assignment operator C& operator=(C&&) = default; // Move assignment operator virtual ~C() { } // Destructor }; |
有点烦人,但可能比备选方案(自动生成所有特殊成员函数)要好。
与三大规则不同,不遵守规则会造成严重损害,不明确声明移动构造函数和移动分配运算符通常很好,但在效率方面往往是次优的。如上所述,只有在没有显式声明的复制构造函数、复制赋值运算符或析构函数的情况下,才会生成移动构造函数和移动赋值运算符。相对于复制构造函数和复制赋值操作符的自动生成,这与传统C++ 03行为不相对称,但更安全。因此,定义移动构造函数和移动赋值运算符的可能性非常有用,并且创造了新的可能性(纯可移动类),但是遵循大03的C++三规则的类仍然很好。
对于资源管理类,如果无法复制基础资源,则可以将复制构造函数和复制分配运算符定义为已删除(算作定义)。通常您仍然需要移动构造函数和移动分配运算符。复制和移动赋值运算符将经常使用EDCOX1×0,如C++ 03实现。如果您有一个移动构造函数和移动赋值运算符,那么专门化
不用于资源管理(即没有非空析构函数)或子类型多态性(即没有虚拟析构函数)的类应声明五个特殊成员函数中的任何一个;它们都将自动生成,并且行为正确、快速。
我真不敢相信没有人跟这有关。
文章基本上主张"零规则"。我不适合引用整篇文章,但我认为这是要点:
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.
此外,该位也很重要:
Common"ownership-in-a-package" classes are included in the standard
library:std::unique_ptr andstd::shared_ptr . Through the use of
custom deleter objects, both have been made flexible enough to manage
virtually any kind of resource.
我不这么认为,三法则是一个经验法则,它指出一个实现了以下其中一个而不是全部的类可能是错误的。
但是,省略move构造函数或move赋值运算符并不意味着存在bug。在优化(大多数情况下)中,这可能是一个错失的机会,或者移动语义与这个类不相关,但这不是一个bug。
虽然在相关的情况下定义移动构造函数可能是最佳实践,但它不是强制的。有许多情况下,一个移动构造函数与一个类不相关(例如,EDCOX1(0)),并且在C++ 03中正确运行的所有类即使在没有定义移动构造函数的情况下也将继续正确地运行在C++ 0x中。
是的,我认为为这些类提供一个移动构造函数是很好的,但是请记住:
这只是一个优化。
只实现一个或两个复制构造函数、赋值运算符或析构函数可能会导致错误,而没有移动构造函数则可能会降低性能。
移动构造函数不能总是在未经修改的情况下应用。
有些类总是分配指针,因此此类类总是删除析构函数中的指针。在这些情况下,您需要添加额外的检查,以确定它们的指针是已分配还是已被移走(现在为空)。
以下是2011年1月24日以来的最新情况和相关发展。
根据C++ 11标准(见附件D的[ DEPR .PyDeC]):
The implicit declaration of a copy constructor is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor. The implicit declaration of a copy assignment operator is deprecated if the class has a user-declared copy constructor or a user-declared destructor.
事实上,它建议废除过时的行为,给予C++ 14一个真正的"五的规则",而不是传统的"三的规则"。在2013,EWG投票反对这一提议将在C++ 2014中实现。对该提案作出决定的主要理由与打破现有准则的普遍关切有关。
最近,又提出了用C++ 11语言来实现五的非正式规则,即
no copy function, move function, or destructor be compiler-generated if any of these functions is user-provided.
如果被EWG认可,则C++可能采用"规则"。
基本上是这样的:如果你不声明任何移动操作,你应该遵守三原则。如果您声明一个移动操作,那么"违反"三条规则不会有任何危害,因为编译器生成的操作的生成受到了非常严格的限制。即使你不声明移动操作,也违反了三的规则,一个C++0x编译器会给你一个警告,如果一个特殊的功能是用户声明的,并且其他特殊的函数已经被自动生成,这是因为现在不推荐的"C++ 03兼容性规则"。
我认为可以肯定地说,这条规则变得不那么重要了。C++ 03中的实际问题是,实现不同的复制语义需要用户声明所有相关的特殊函数,使得它们都不是编译器生成的(否则会出错)。但是C++ 0x改变了特殊成员函数生成的规则。如果用户声明这些函数中的一个来更改复制语义,它将阻止编译器自动生成其余的特殊函数。这很好,因为丢失的声明会立即将运行时错误转换为编译错误(或者至少是警告)。作为C++ 03兼容性度量,仍会生成一些操作,但这一代被认为是弃用的,并且至少应该在C++ 0x模式下产生警告。
由于编译器生成的特殊函数和C++ 03兼容性的限制性规则,三的规则保持为三的规则。
下面是一些最新的C++0X规则应该很好的例子:
1 2 3 4 5 6 7 8 9 10 | template<class T> class unique_ptr { T* ptr; public: explicit unique_ptr(T* p=0) : ptr(p) {} ~unique_ptr(); unique_ptr(unique_ptr&&); unique_ptr& operator=(unique_ptr&&); }; |
在上面的示例中,不需要将任何其他特殊函数声明为已删除。因为这些限制性规则,它们不会被生成。用户声明的移动操作的存在将禁用编译器生成的复制操作。但在这种情况下:
1 2 3 4 5 6 7 8 | template<class T> class scoped_ptr { T* ptr; public: explicit scoped_ptr(T* p=0) : ptr(p) {} ~scoped_ptr(); }; |
现在,C++0x编译器可能会对可能产生错误的编译器生成的复制操作发出警告。在这里,三管齐下,应该受到尊重。在这种情况下,警告是完全合适的,并给用户处理错误的机会。我们可以通过删除的功能来解决问题:
1 2 3 4 5 6 7 8 9 10 | template<class T> class scoped_ptr { T* ptr; public: explicit scoped_ptr(T* p=0) : ptr(p) {} ~scoped_ptr(); scoped_ptr(scoped_ptr const&) = delete; scoped_ptr& operator=(scoped_ptr const&) = delete; }; |
因此,三的规则仍然适用于这里,因为C++ 03的兼容性。
我们不能说3规则现在变成4(或5)规则,而不破坏所有执行3规则且不实现任何形式的移动语义的现有代码。
规则3意味着如果你实现了一个,你必须实现所有3个。
也不知道会有任何自动生成的移动。"规则3"的目的是因为它们自动存在,如果您实现了其中一个,那么其他两个的默认实现很可能是错误的。
在一般情况下,那么是的,三的规则变成了五的规则,添加了移动分配操作符和移动构造函数。然而,并不是所有的类都是可复制和可移动的,有些类只是可移动的,有些类只是可复制的。