虚析构函数会被继承吗?

90

如果我有一个带有虚析构函数的基类,那么派生类是否需要声明虚析构函数?

class base {
public:
    virtual ~base () {}
};

class derived : base {
public:
    virtual ~derived () {} // 1)
    ~derived () {}  // 2)
};

具体问题:

  1. 第1)和第2)是否相同?第2)是否会自动变成虚函数,因为它是基类的,或者它会“停止”是虚函数?
  2. 如果派生析构函数没有任何作用,是否可以省略它?
  3. 如何声明派生析构函数最佳实践?将其声明为虚函数、非虚函数还是尽可能省略它?
5个回答

103
  1. 是的,它们是相同的。即使派生类没有声明某个方法为虚函数,也不能阻止它成为虚函数。事实上,如果一个基类中的方法(包括析构函数)是虚函数,那么在派生类中无法阻止它成为虚函数。在C++11及其以上版本中,可以使用final关键字防止其在派生类中被重写,但这并不会阻止它成为虚函数。
  2. 是的,在派生类中如果析构函数没有任何内容可实现,则可以省略掉它。而是否将其声明为虚函数并不影响。
  3. 如果可能的话,我会省略掉它。对于派生类中的虚函数,我总是使用virtual关键字或override关键字来增加代码的清晰性。人们不应该必须沿着继承层次结构往上查找才能确定一个函数是否是虚函数。另外,如果你的类是可复制或可移动的,而不需要声明自己的复制或移动构造函数,那么声明任何一种类型的析构函数(即使将其定义为default)都会强制你声明复制和移动构造函数以及赋值运算符,如果你需要它们,因为编译器不会再为你生成这些函数。

对于第3点的一小点说明。评论中指出,如果析构函数未声明,则编译器将生成一个默认的析构函数(仍然是虚函数)。而这个默认的析构函数是内联函数。

内联函数有可能将程序暴露给其他部分的更改,并使得共享库的二进制兼容性变得棘手。此外,增加的耦合性可能在面对某些类型的更改时导致大量的重新编译。例如,如果你决定你真的需要实现一个虚析构函数,那么调用它的每一行代码都需要被重新编译。而如果你在类体中声明了它,然后在.cpp文件中将其定义为空,那么即使更改它也不需要重新编译。

我的个人选择仍然是尽可能省略它。在我看来,这会使代码变得混乱,并且编译器有时可以使用默认实现而不是空实现执行稍微更有效的操作。但是你可能会面临一些约束,使得这种选择不太合适。


1
我不同意“省略”部分。在头文件中声明它并在源文件中定义它(空函数体)并不会花费太多时间。如果这样做,您可以随时回来添加一些步骤(例如日志记录),而无需强制客户重新编译。 - Matthieu M.
1
实际上,我很少声明许多内联函数,甚至不使用经典的“访问器”,但在大公司工作时,我们可能会面临比大多数人更高的二进制兼容性约束。 - Matthieu M.
4
我刚从这个讲座中学到,声明虚拟析构函数会导致您的类变得无法移动!因此,如果您想要这些属性,每次声明虚拟析构函数时都必须提供整个“规则五”。这更加说明应尽可能省略虚拟析构函数。 - Neil Traft
1
此外,如果您的类可以在不声明自己的复制或移动构造函数的情况下进行复制或移动,则声明任何类型的析构函数(即使将其定义为默认值)都会强制您声明复制和移动构造函数以及赋值运算符,如果您希望它们存在,因为编译器将不再为您自动添加它们。 - Kaiserludi
1
@Kaiserludi - 我会再次确认这是否正确,并修正我的回答。 - Omnifarious
显示剩余7条评论

2

虚成员函数将隐式地使任何对该函数的重载变为虚函数。

因此,在1)中,"虚"是可选的,基类析构函数是虚的,这也使得所有子类的析构函数都是虚的。


2
  1. 析构函数在C++中自动成为虚函数,与所有方法一样。你无法阻止一个方法成为虚函数(如果它已经被声明为虚函数,也就是说,在Java中没有'final'的等效物)
  2. 是的,可以省略。
  3. 如果我打算将这个类作为子类,无论是不是子类化另一个类,我都会声明一个虚拟析构函数,我也更喜欢保持声明方法为虚拟的状态,即使不需要。这将保持子类的工作,如果您决定删除继承关系。但我想这只是一个风格问题。

1
析构函数不会自动变成虚函数,其他任何成员函数也不会。 - anon
2
@Neil;当然不是,我指的是示例中的“the”析构函数(即基类有一个虚拟析构函数),而不是一般的析构函数。这对于所有方法都是正确的,而不仅仅是析构函数。 - falstro
1
自从C++11以来,我们拥有了final - whoan

0

1/ 是的 2/ 是的,它将由编译器生成 3/ 声明为虚函数还是非虚函数应该遵循您对重写虚成员的约定--在我看来,两种方式都有好的论点,只需选择一种并遵循它。

如果可能的话,我会省略它,但有一件事可能会促使您声明它:如果使用编译器生成的函数,则它隐式地是内联的。有时您想避免内联成员(例如动态库)。


0

虚函数会被隐式地覆盖。当子类的方法与基类的虚函数方法签名匹配时,它就会被覆盖。

这很容易在重构期间混淆和可能破坏,因此自C++11以来有overridefinal关键字来明确标记此行为。有相应的警告禁止静默行为,例如GCC中的-Wsuggest-override

SO上有一个相关问题,涉及到overridefinal关键字:Is the 'override' keyword just a check for a overridden virtual method?

还有cpp参考文档中的文档https://en.cppreference.com/w/cpp/language/override

是否在析构函数中使用override关键字仍存在一些争议。例如,请参见此相关SO问题的讨论:默认虚析构函数的覆盖 问题在于,虚析构函数的语义与普通函数不同。析构函数是链接的,因此所有基类析构函数都在子类之后被调用。然而,在常规方法的情况下,默认情况下不会调用重写方法的基本实现。它们可以在需要时手动调用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接