如何正确实现 C++ class 析构函数

How to properly implement a C++ class destructor

在class(没有直接指针成员)中,我看到定义析构函数有以下3种可能性。

class Child : public Parent
{
public:
    // ~Child() override {}          // (1) explicit destructor with empty body
    // ~Child() override = default;  // (2) explicit default destructor
    //                               // (3) implicit default destructor


private:
    // members
}

Can/should选项(1)总是被避免?因为如果我使用选项 (1).

,Clang-Tidy 会提示我选择 (2)

这三种不同的选择大体上有什么区别?选择一个而不是其他时应该考虑什么?

Can/should option (1) always be avoided?

如果你没有什么可以放入析构函数,那么你应该让编译器为你生成一个default析构函数,所以是的。

What are the differences between the three different options in general?

假设没有什么特别的东西需要放入你的析构函数中:

  1. 您将析构函数锁定为空,如果情况发生变化您可能会忘记将它们添加到析构函数中。

  2. 让编译器解决所有问题并在代码中显示您这样做。

  3. 让编译器自行解决,不要在代码中显示您这样做。

Can/should option (1) always be avoided?

假设语言不是古代版本,是的。据我所知,使用空的非默认析构函数的唯一原因是支持 C++03 和更旧的标准。

What should be considered when selecting one over others?

    1. 和 3. 具有在所有版本的 C++ 中都有效的优势(贬低 override 说明符)。
    1. 和 3. 只要成员是平凡可破坏的,就具有平凡的优势。
    1. 和 2. 的优点是允许析构函数与 class 定义分开定义(示例中未利用)。这是至关重要的,例如,如果您有一个指向不完整类型的唯一指针作为成员。这在实现 PIMPL 模式时很典型。
    1. 和 2. 还具有允许析构函数显式声明为虚拟的优点,这对于多态基 classes.
    2. 通常是必需的
    1. 缺点是不推荐使用隐式声明的复制构造函数和赋值运算符。这意味着不应依赖它,并且将来可能会停止工作。 1. 和 2. 都有阻止隐式移动构造函数和赋值运算符生成的缺点。因此,如果您使用其中任何一个,那么您还应该声明复制和移动构造函数以及赋值运算符(如果可能的话,默认为默认值)。
    1. 具有写入最少和读取最少的优点,尤其是考虑到上一段。

作为粗略的经验法则,如果可能,请使用 3.。如果不可能(例如,上面描述的 PIMPL 情况),则使用 2。如果不可能(即您需要支持 C++03),则使用 1.

C++ 使用 RAII principle. Closely related there is The rule of three/five/zero.

它是这样的:

  • 如果您的 class 是资源的所有者,那么它必须实现所有 3/5 特殊成员(copy/move 构造函数,copy/move 赋值和析构函数)以尊重RAII 原则
  • 如果您需要至少定义一个 3/5 特殊成员,那么您的 class 很可能拥有资源,因此您必须定义所有 3/5
  • 如果您的 class 拥有资源,那么它必须专门处理该资源的预订。否则它不应该定义任何提到的特殊成员(零规则)。

您的所有代码都应该属于零规则领域。所以你需要有隐式析构函数。