c++ 虚析构函数的缺省重写

inkz8wg9  于 11个月前  发布在  其他
关注(0)|答案(7)|浏览(126)

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

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

};

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

字符串
在Child类的析构函数中同时使用关键字“override”和“=default”是否正确?在这种情况下,编译器是否会生成正确的虚拟析构函数?
如果是,那么我们是否可以认为这是一种好的编码风格,并且我们应该始终以这种方式声明派生类的析构函数,以确保基类析构函数是虚拟的?

xfyts7mz

xfyts7mz1#

在Child类的析构函数中同时使用关键字“override”和“=default”是否正确?在这种情况下,编译器是否会生成正确的虚拟析构函数?
是的,它是正确的。在任何正常的编译器上,如果代码编译无误,这个析构函数定义将是一个空操作:它的缺失不能改变代码的行为。
我们可以认为它是好的编码风格吗?
这是一个偏好的问题。对我来说,只有当基类类型被模板化时才有意义:那么,它将强制要求基类具有虚析构函数。否则,当基类被修复时,我认为这样的代码是噪音。它并不像基类会神奇地改变。但是如果你有一些愚蠢的队友,他们喜欢在不检查代码的情况下改变东西,而这些代码取决于他们可能破坏的东西,那么最好保留析构函数定义,作为额外的保护层。

vsaztqbk

vsaztqbk2#

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

1tuwyuhd

1tuwyuhd3#

根据CppCoreGuidelines C.128,派生类的析构函数不应该声明为virtualoverride
如果基类的析构函数声明为virtual,则应避免声明派生类的析构函数virtualoverride。一些代码库和工具可能坚持重写析构函数,但这不是本指南的建议。

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

Method overriding是一种语言特性,它允许子类或子类提供一个方法的特定实现,该方法已经由它的超类或父类提供。子类中的实现通过提供与父类中的方法具有相同名称,相同参数或签名以及相同返回类型的方法来覆盖(替换)超类中的实现。
换句话说,当你调用一个被覆盖的方法时,只有该方法的最后一个实现(在类层次结构中)实际上被执行,而所有析构函数(从最后一个子对象到根父对象)必须被调用,以正确地释放对象拥有的所有资源。
因此,我们并没有真正替换(覆盖)析构函数,我们在对象析构函数链中添加了一个额外的析构函数。

更新:CppCoreGuidelines C.128规则被修改(通过14481446问题),以简化已经详尽的例外列表。因此,一般规则可以总结为:

对于类用户,所有虚函数包括析构函数都是多态的。
在拥有状态的子类上标记析构函数override是教科书上的卫生习惯,你都应该通过例程来做(参考)。

hkmswyz6

hkmswyz64#

在这里使用override(至少)有一个原因--确保基类的析构函数总是虚的。如果派生类的析构函数认为它正在重写某些东西,但没有什么可重写的,这将是一个编译错误。如果你这样做,它也给了你一个方便的地方来保留生成的文档。

  • 另一方面,* 我可以想到两个理由不这样做:
  • 派生类强制执行基类的行为有点奇怪和倒退。
  • 如果你在头文件中定义了一个destuctor(或者你把它内联了),你确实会引入奇怪的编译错误的可能性。假设你的类看起来像这样:
struct derived {
    struct impl;
    std::unique_ptr<derived::impl> m_impl;
    ~derived() override = default;
};

字符串
您可能会得到一个编译器错误,因为析构函数(与这里的类内联)将查找不完整类derived::impl的析构函数。
这是我迂回的说法,每一行代码都可能成为一种负担,如果它在功能上什么也不做,也许最好跳过一些东西。如果你真的真的需要从父类在基类中强制一个虚析构函数,有人建议使用static_assertstd::has_virtual_destructor,这将产生更一致的结果,恕我直言。

jtjikinw

jtjikinw5#

我认为“override”在析构函数上有点误导。当你重写虚函数时,你替换了它。析构函数是链接的,所以你不能直接重写析构函数

jum4pzuy

jum4pzuy6#

CPP Reference表示override确保函数是virtual,并且它确实覆盖了一个虚函数。因此override关键字将确保析构函数是虚的。
如果你指定了override而不是= default,那么你将得到一个链接器错误。
你不需要做任何事情。保持Child dtor未定义就可以了:

#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,它将不会输出任何内容,因为这是未定义的行为,正如注解中所指出的那样。
如果你不相信基类声明了它是虚的,那么你的override= default的建议是可行的;我希望有更好的方法来确保这一点,而不是用那些析构函数声明来乱丢代码。

km0tfn4u

km0tfn4u7#

虽然析构函数不是继承的,但标准中明确规定派生类的虚析构函数覆盖基类的析构函数。
C标准(10.3虚函数)
6即使析构函数不是继承的,派生类中的析构函数也会覆盖声明为virtual的基类析构函数;参见12.4和12.5。
另一方面,也有写(9.2类成员)
8 virt-specifier-seq最多只能包含一个virt-specifier。virt-specifier-seq只能出现在虚成员函数的声明中(10.3)。
虽然析构函数像特殊的成员函数一样被调用,但它们也是成员函数。
我确信C
标准应该被编辑成这样一种方式,即明确析构函数是否可以有virt-specifier override

相关问题