虚拟析构函数的默认重写

每个人都知道基类的析构函数通常是虚的。但是派生类的析构函数是什么呢?在 C + + 11中,我们有关键字“覆盖”和显式使用默认析构函数的能力。

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


};


struct Child: public Parent
{
std::string b;
~Child() override = default;
};

在 Child 类的析构函数中同时使用关键字“覆盖”和“ = default”是否正确?在这种情况下,编译器会生成正确的虚析构函数吗?

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

49020 次浏览

override只不过是一张安全网。如果基类析构函数是虚的,则子类的析构函数将始终是虚的,不管它是如何声明的——或者根本没有声明(例如使用隐式声明的一个)。

参考资料说,override确保函数是 virtual,并且它确实覆盖了一个虚函数。因此,override关键字将确保析构函数是虚的。

如果指定的是 override而不是 = default,那么将得到一个链接器错误。

你不需要做任何事情。留下 Child医生未定义的工作就可以了:

#include <iostream>


struct Notify {
~Notify() { std::cout << "dtor" << std::endl; }
};


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


struct Child : public Parent {
std::string b;
Notify n;
};


int main(int argc, char **argv) {
Parent *p = new Child();
delete p;
}

输出 dtor。但是,如果你删除了 Parent::~Parent处的 virtual,它将不会输出任何东西,因为它是未定义行为的,就像评论中指出的那样。

好的风格是根本不提 Child::~Child。如果您不能相信基类声明它是虚的,那么您使用 override= default的建议将会起作用; 我希望有更好的方法来确保这一点,而不是用那些析构函数声明乱丢代码。

虽然析构函数不是继承的,但是在标准中明确写明,派生类的虚析构函数会覆盖基类的析构函数。

来自 C + + 标准(10.3虚函数)

6即使析构函数不是继承的,派生的 类 重写声明为虚的基类析构函数; 参见12.4和 12.5.

另一方面也有写(9.2类成员)

一个 virt 说明符 seq 最多只能包含每个 virt 说明符中的一个。 Virt 说明符 -seq 只应出现在 < strong > a 的声明中 虚拟成员函数 (10.3) .

虽然析构函数像特殊的成员函数一样被调用,但它们也是成员函数。

我确信 C + + 标准应该以这样一种方式进行编辑,即一个析构函数是否可能具有 virt 说明符 override是明确的。目前还不清楚。

在 Child 类的析构函数中同时使用关键字“覆盖”和“ = default”是否正确?在这种情况下,编译器会生成正确的虚析构函数吗?

是的,没错。在任何正常的编译器上,如果代码编译没有错误,那么这个析构函数定义将是一个 no-op: 它的缺失不能改变代码的行为。

我们可以认为它是良好的编码风格

这只是个人喜好的问题。对我来说,只有当基类类型是模板化的时候才有意义: 那么它将对基类强制要求具有虚析构函数。否则,当基类型固定时,我会认为这样的代码是噪声。基类并不会神奇地改变。如果您有一些死脑筋的队友,他们喜欢在不检查代码的情况下进行更改,而这些代码取决于他们可能破坏了什么,那么最好保留析构函数定义——作为一个额外的保护层。

在这里使用 override至少有一个原因——确保基类的析构函数始终是虚的。如果派生类的析构函数认为它正在重写某些内容,但没有可重写的内容,那么这将是一个编译错误。如果您正在这样做,它还为您提供了一个方便的地方来保存生成的文档。

另一方面,我能想到两个不这么做的理由:

  • 对于派生类来说,从基类强制执行行为有点怪异,而且是向后的。
  • 如果在消息头中定义了一个析构函数(或者将其内联) ,则可能会出现奇数编译错误。假设你的班级是这样的:

    struct derived {
    struct impl;
    std::unique_ptr<derived::impl> m_impl;
    ~derived() override = default;
    };
    

    您可能会得到一个编译器错误,因为析构函数(与这里的类内联)将查找不完整类 derived::impl的析构函数。

    这是我拐弯抹角的说法,即每一行代码都可能成为一种负担,如果某些代码在功能上什么也不做,那么最好跳过它。如果您真的需要在父类的基类中强制使用虚析构函数,有人建议使用 static_assertstd::has_virtual_destructor协同工作,这将产生更一致的结果,IMHO。

我觉得“覆盖”这个词有点误导人。 重写虚函数时,需要替换它。 破坏程序是链接的,所以你不能直接覆盖破坏程序

根据 CppCore 准则 C.128,派生类的析构函数不应该声明为 virtualoverride

如果基类析构函数被声明为虚类,则应避免声明派生类析构函数 virtualoverride。一些代码库和工具可能坚持对析构函数进行重写,但这不是这些准则的建议。

更新 : 为了回答为什么我们对析构函数有特殊情况的问题。

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

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

因此,我们不会真正替换(覆盖)析构函数,而是在对象析构函数链中添加一个。

更新 : 为了简化已经详尽无遗的异常列表,对 CppCore 准则 C.128规则进行了更改(通过 14481446问题)。因此,一般规则可以概括为:

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

在国家拥有的子类上标记析构函数 override是教科书卫生,你们都应该通过例行程序来做(裁判。)。