【问题标题】:How to properly implement a C++ class destructor如何正确实现 C++ 类析构函数
【发布时间】:2019-10-16 11:36:35
【问题描述】:

在一个类中(没有直接指针成员),我看到定义析构函数有以下 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
}

可以/应该始终避免选项(1) 吗?因为如果我使用选项 (1),Clang-Tidy 会提示我选择选项 (2)

这三个不同的选项一般有什么区别?选择一个而不是其他应该考虑什么?

【问题讨论】:

  • 还有第四版~Child() override;,在源文件中有单独的定义,例如Child::~Child() = default。例如,这对于 PIMPL-via-unique-ptr 很重要,其中指针的值类型必须在析构函数中完整(参见 eerorika 的答案)。你的 3 个版本都不能在这里工作。
  • 我得到的指导是使用~Child();(没有虚拟或覆盖标签),或者如果析构函数的默认行为已经正确,因为没有要释放的资源,那就更好了,根本不提供。
  • @MaxLanghof 假设代码是可编译的,override 关键字意味着Parent(或其父级之一)具有virtual 析构函数。还是我把你的问题弄错了?
  • @Sumudu 你是对的,我不知何故完全错过了override。抱歉打扰了!

标签: c++ memory-management c++14 destructor virtual-destructor


【解决方案1】:

可以/应该始终避免选项 (1) 吗?

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

选择一个而不是其他应该考虑什么?

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

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

【讨论】:

  • 另外,1.(还有 2.,我认为)有一个缺点是禁用隐式生成移动 ctor/assignment。
  • @HolyBlackCat 不错。我检查了规则,并添加到答案中。
【解决方案2】:

是否可以/应该始终避免选项 (1)?

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

三个不同的选项一般有什么区别?

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

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

  2. 让编译器弄清楚并在代码中显示你这样做。

  3. 让编译器把它弄清楚,不要在代码中显示你这样做。

【讨论】:

    【解决方案3】:

    C++ 使用RAII 原则。密切相关的是The rule of three/five/zero

    是这样的:

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

    您绝对属于零规则,就像您的所有代码一样。所以你需要有隐式析构函数。

    【讨论】:

      猜你喜欢
      • 2012-05-08
      • 2018-06-20
      • 2015-08-03
      • 2012-01-20
      • 2011-04-06
      • 1970-01-01
      • 1970-01-01
      • 2021-12-13
      • 1970-01-01
      相关资源
      最近更新 更多