08 August 2022

C++ 核心指南目录

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的;要么一个也不声明,由编译器隐式生成。