C.21:默认操作要定义就全定义,要禁止就全禁止
The special member functions are the default constructor, copy constructor, copy assignment operator, move constructor, move assignment operator, and destructor.
特殊的成员函数包括构造函数,拷贝构造函数,拷贝赋值运算符,移动构造函数,移动赋值运算符和析构函数。
译者注:这些函数都具有管理数据成员生命周期的责任,因此实现还是禁止都需要统一。
The semantics of the special functions are closely related, so if one needs to be declared, the odds are that others need consideration too.
特殊函数的语义紧密相关,如果一个需要声明,可能其他的也需要考虑。
Declaring any special member function except a default constructor, even as =default or =delete, will suppress the implicit declaration of a move constructor and move assignment operator. Declaring a move constructor or move assignment operator, even as=default or =delete, will cause an implicitly generated copy constructor or implicitly generated copy assignment operator to be defined as deleted. So as soon as any of the special functions is declared, the others should all be declared to avoid unwanted effects like turning all potential moves into more expensive copies, or making a class move-only.
定义除默认构造函数之外的所有特殊函数,即使采用=default或者=delete的形式,将会抑制隐式声明移动构造函数和移动赋值运算符。声明移动构造函数或者移动赋值运算符,即使采用=default或者=delete的形式,也会导致隐式生成的拷贝构造函数或者拷贝赋值运算符被定义为=delete。因此,一旦任何一个特殊函数被声明,其他的都应该被声明以避免多余的效果。例如将所有的潜在移动操作都被变成代价高昂的拷贝操作,或者令这个类变成只移动的。
译者注:关于=default和=delete请参考以下链接:
https://mp.weixin.qq.com/s/YgyRdwgkcKdbbmMAbU8Amg
https://mp.weixin.qq.com/s/5yMBZWlKN_7OWhaJD4u_XQ
struct M2 { // bad: incomplete set of default operations
public:
// ...
// ... no copy or move operations ...
~M2() { delete[] rep; }
private:
pair<int, int>* rep; // zero-terminated set of pairs
};
void use()
{
M2 x;
M2 y;
// ...
x = y; // the default assignment
// ...
}
Given that "special attention" was needed for the destructor (here, to deallocate), the likelihood that copy and move assignment (both will implicitly destroy an object) are correct is low (here, we would get double deletion).
假设析构函数需要那个“特殊模式”(这里是释放内存),那么(默认的,译者注)拷贝和移动赋值(都会隐性销毁对象)正确动作的可能性就会很低。
This is known as "the rule of five" or "the rule of six", depending on whether you count the default constructor.
这就是众所周知的"5特殊函数规则"或者"6特殊函数规则",不同之处在于是否将默认构造函数算进来。
Note(注意)
If you want a default implementation of a default operation (while defining another), write =default to show you're doing so intentionally for that function. If you don't want a default operation, suppress it with =delete.
如果需要默认操作的默认实现(例如定义了其他非默认的),通过=default表示你是有意那么做的。如果不想要默认操作,通用=delete抑制它的产生。
译者注:例如,如果定义了某种形式的构造函数,编译器就不会生成默认的构造函数。
Example, good(示例)
When a destructor needs to be declared just to make it virtual
, it can be defined as defaulted. To avoid suppressing the implicit move operations they must also be declared, and then to avoid the class becoming move-only (and not copyable) the copy operations must be declared:
如果需要声明析构函数就直接定义为virtual,这个做法可以作为默认。为了避免抑制隐式的移动操作,它们也必须被声明。为了避免类成为只移动(和拷贝禁止)类型,拷贝操作也必须声明:
class AbstractBase {
public:
virtual ~AbstractBase() = default;
AbstractBase(const AbstractBase&) = default;
AbstractBase& operator=(const AbstractBase&) = default;
AbstractBase(AbstractBase&&) = default;
AbstractBase& operator=(AbstractBase&&) = default;
};
Alternatively to prevent slicing as per C.67, the copy and move operations can all be deleted:
为了避免由于规则C.67产生的分歧,也可以将拷贝和移动运算符定义为删除的。
class ClonableBase {
public:
virtual unique_ptr<ClonableBase> clone() const;
virtual ~ClonableBase() = default;
ClonableBase(const ClonableBase&) = delete;
ClonableBase& operator=(const ClonableBase&) = delete;
ClonableBase(ClonableBase&&) = delete;
ClonableBase& operator=(ClonableBase&&) = delete;
};
Defining only the move operations or only the copy operations would have the same effect here, but stating the intent explicitly for each special member makes it more obvious to the reader.
只定义移动操作或者拷贝操作产生的效果相同,但是应该明确地为每个特殊函数说明目的以便让读者更容易理解。
Compilers enforce much of this rule and ideally warn about any violation.
编译器会强制执行本规则的大部分,理想情况会对任何违反发出警告。
Note(注意)
Relying on an implicitly generated copy operation in a class with a destructor is deprecated.
强烈反对一个具有析构函数的类依靠隐式产生的拷贝操作。
Writing the six special member functions can be error prone. Note their argument types:
同时写6个特殊成员函数容易发生错误。注意以下代码中的参数类型。
class X {
public:
// ...
virtual ~X() = default; // destructor (virtual if X is meant to be a base class)
X(const X&) = default; // copy constructor
X& operator=(const X&) = default; // copy assignment
X(X&&) = default; // move constructor
X& operator=(X&&) = default; // move assignment
};
A minor mistake (such as a misspelling, leaving out a const
, using &
instead of &&
, or leaving out a special function) can lead to errors or warnings. To avoid the tedium and the possibility of errors, try to follow the rule of zero.
小错误(例如拼写错误,落了const,用了&而不是&&,或者落了某个特殊成员函数)会引起错误或警告。为了避免无聊的代码和可能的错误,努力践行"0特殊函数"原则。
Enforcement(实施建议)
(Simple) A class should have a declaration (even a =delete one) for either all or none of the special functions.
(简单)类应该要么声明(哪怕是通过=delete)所有的特殊函数,要么一个也不声明。
原文链接:
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c21-if-you-define-or-delete-any-default-operation-define-or-delete-them-all
觉得本文有帮助?请分享给更多人。
关注【面向对象思考】轻松学习每一天!
面向对象开发,面向对象思考!