虚拟析构函数的默认覆盖

San*_*dro 35 c++ c++11 c++14

每个人都知道基类的析构函数通常必须是虚拟的.但是衍生类的析构函数是什么?在C++ 11中,我们有关键字"override"和显式使用默认析构函数的能力.

struct Parent
{
  std::string a;
  virtual ~Parent()
  {
  }

};

struct Child: public Parent
{
  std::string b;
  ~Child() override = default;
};
Run Code Online (Sandbox Code Playgroud)

在Child类的析构函数中使用关键字"override"和"= default"是否正确?在这种情况下编译器会生成正确的虚析构函数吗?

如果是,那么我们可以认为它是好的编码风格,我们应该总是以这种方式声明派生类的析构函数,以确保基类析构函数是虚拟的吗?

Rei*_*ica 18

在Child类的析构函数中使用关键字"override"和"= default"是否正确?在这种情况下编译器会生成正确的虚析构函数吗?

是的,这是正确的.在任何理智的编译器上,如果代码编译没有错误,这个析构函数定义将是一个无操作:它的缺席不能改变代码的行为.

我们可以认为这是一种很好的编码风格

这是一个偏好问题.对我来说,只有基类类型是模板化的才有意义:它将强制要求基类具有虚拟析构函数.否则,当基类型固定时,我会认为这样的代码是噪声.它不像基类会神奇地改变.但是如果你有一些死去的队友喜欢在没有检查代码的情况下改变它们,而这些代码依赖于它们可能会破坏的东西,那么最好将析构函数定义留在 - 作为额外的保护层.

  • 请注意,有时候,你是你自己的头脑冷静的队友:( (12认同)
  • 你不需要魔法,你只需要那些死去的队友;) (5认同)

Ser*_*eyA 12

override只不过是一个安全网.如果基类析构函数是虚拟的,子类的析构函数将始终是虚拟的,无论它是如何声明的 - 或者根本不声明(即使用隐式声明的析构函数).

  • 问题是基类必须具有虚拟析构函数但却不正确的情况. (4认同)
  • @SergeyA这不是问题的答案. (4认同)

小智 8

我认为“覆盖”对析构函数有点误导。当你重写虚函数时,你就替换了它。析构函数是链接的,所以你不能从字面上覆盖析构函数

  • 我不会这么说。重写的函数应该执行相同的语义任务(尽管派生版本应该更专业)。而且替换也不是完全的。它只是意味着默认情况下调用解析为对象的类型。您仍然可以使用作用域运算符显式调用基类函数。请参阅此处/sf/ask/2660720051/ (3认同)
  • Override 指定基类实现的必需属性:该函数必须是虚拟的。实际上完全合理地说:`~Derived() override = default;` 使用 override 是保证基类正确定义的唯一方法。当声明从模板参数派生的模板并且需要保证基类正确声明其析构函数时,这一点尤其重要。 (3认同)
  • 在英语语义意义上,您可能是对的,覆盖(“中断操作,以便进行手动控制”)析构函数是没有意义的。在 C++ 语义意义上(“要求基类方法是虚拟的”),将析构函数标记为重写是有意义的,并且具有特定的实用性,正如我提到的 (3认同)

Ale*_* A. 8

根据CppCoreGuidelines C.128,不应声明派生类的析构函数virtualoverride

如果基类析构函数被声明为 virtual,则应避免声明派生类析构函数virtualoverride. 一些代码库和工具可能会坚持覆盖析构函数,但这不是这些指南的建议。

更新:回答为什么我们有一个特殊的析构函数的问题。

方法覆盖是一种语言功能,它允许子类或子类提供已由其超类或父类之一提供的方法的特定实现。子类中的实现通过提供与父类中的方法具有相同名称、相同参数或签名以及相同返回类型的方法来覆盖(替换)超类中的实现。

换句话说,当你调用一个被覆盖的方法时,只有该方法的最后一个实现(在类层次结构中)被实际执行,而所有的析构函数(从最后一个子级到根父级)必须被调用以正确释放所有拥有的资源由对象。

因此,我们并没有真正替换(覆盖)析构函数,而是将额外的一个添加到对象析构函数链中。

更新:此CppCoreGuidelines C.128规则已更改(由14481446问题)以简化已经详尽的例外列表。所以一般规则可以概括为:

对于类用户,包括析构函数在内的所有虚函数都是同样多态的。

override在国有子类上标记析构函数是教科书式的卫生,你们都应该按例行事(参考文献)。

  • @reddy 好吧,看起来他们改变了主意。您可以在这里找到更多信息:https://github.com/isocpp/CppCoreGuidelines/issues/1446。我可能会稍后更新我的答案。 (4认同)
  • 很有意思!但是他们没有解释为什么我们有一个特殊的析构函数,所有其他函数都应该被覆盖或最终...... (2认同)
  • C.128没有这么说,它根本没有说任何关于析构函数的事情 (2认同)
  • 迄今为止最有价值的答案。唯一的问题是必须阅读所有**更新**部分才能理解这一点。如果你有时间,我会很高兴看到这个问题的重做。在一开始就给出最终版本的提示可能就足够了。 (2认同)

cyb*_*son 6

override这里使用(至少)一个原因- 确保基类的析构函数始终是虚拟的.如果派生类的析构函数认为它覆盖了某些东西,那将是一个编译错误,但没有什么可以覆盖.如果您这样做,它还为您提供了一个方便的位置来保留生成的文档.

另一方面,我可以想到两个不这样做的理由:

  • 对于派生类来说,从基类强制执行行为有点奇怪和倒退.
  • 如果在标题中定义了一个destuctor(或者如果它是内联的),那么你确实会引入奇怪的编译错误.假设您的课程如下:

    struct derived {
        struct impl;
        std::unique_ptr<derived::impl> m_impl;
        ~derived() override = default;
    };
    
    Run Code Online (Sandbox Code Playgroud)

    您可能会遇到编译器错误,因为析构函数(此处与类内联)将查找不完整类的析构函数derived::impl.

    这是我的一种说法,即每行代码都可以成为一种负担,也许最好只是跳过一些东西,如果它在功能上什么也不做.如果你真的真的需要强制执行从父类的基类虚析构函数,有人建议使用static_assert在音乐会std::has_virtual_destructor,这将产生更一致的结果,恕我直言.

  • 这个问题的解决方案(前向声明的类)是在编译单元中实现析构函数。例如,在您的 .cpp 文件中, `衍生::~衍生() = default;` 您可以在编译单元中使用 `= default` 。 (2认同)