CppCoreGuidelines C.21 复制、移动和析构函数要同时定义或同时删除
C.21: If you define or =delete
any copy, move, or destructor function,
define or =delete
them all
理由
移动、复制、析构函数紧密相关,所以定义了其中任何一个,就要考虑是否需要定义其他函数。
声明任何一个移动、复制、析构函数,就阻止编译器生成隐含的移动构造、移动赋值操作。即使是把其中一个函数声明成= default
或 = delete
,也会产生一样的效果。
声明移动构造、或移动赋值函数,就会隐含地导致复制构造函数、或复制赋值函数被定义为删除状态。所以,声明了其中任意一个函数,就要同时定义其他函数,以确保不会产生不想要的效果,比如把本可以移动的变成复制(开销更大);使得一个类变成只能进行移动操作。
例子
struct M2 { // bad: incomplete set of copy/move/destructor 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 // ... }
在析构函数里,有一些额外操作,即删除成员指针指向的数据。这会导致默认复制和移动赋值操作出错(指针删除两次)。
注意
此规则又叫“五”规则。即五个相关的函数要一起定义,或全都默认。
注意
如果你想要某个函数为默认实现,请在函数后面写上= default
,标明你的目的。如果你不想要自动生成的默认函数,请添加= delete
。
例子
如果需要声明一个构造函数,可以将它标记为 virtual
,可以设置为默认定义的。
class AbstractBase { public: virtual void foo() = 0; // at least one abstract method to make the class abstract virtual ~AbstractBase() = default; // ... };
为避免多态类实例的数据被切片,导致数据不统一,请将复制和移动操作设置为
protected
或= delete
,并增加一个 clone
函数。
class ClonableBase { public: virtual unique_ptr<ClonableBase> clone() const; virtual ~ClonableBase() = default; CloneableBase() = default; ClonableBase(const ClonableBase&) = delete; ClonableBase& operator=(const ClonableBase&) = delete; ClonableBase(ClonableBase&&) = delete; ClonableBase& operator=(ClonableBase&&) = delete; // ... other constructors and functions ... };
如果只定义移动操作或只定义复制操作,也可以实现一样的效果。但是最好还是按照以上例子,把代码写得清晰一些。
注意
编译器通常会强化此规则,更理想的是能够针对违规提出警告。
注意
以下情况已经不再支持:只有析构函数,然后想要有隐式的拷贝操作
注意
编写以下函数很容易出错。注意他们的参数类型:
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 };
一个小错误(比如拼写错误、忘记写 const
、使用&
的地方用了&&
、忘记写某个特殊函数)都会导致错误和报警。为避免此类繁琐且易出错的工作,请考虑遵循“最小化原则”。
强化
- (简单)一个类要么定义了所有五个特殊函数(复制构造/复制赋值/移动构造
/移动赋值/析构),哪怕其中有标记为
= delete
的;要么一个也不声明,由编译器隐式生成。