83

如果我有一个带有虚拟析构函数的基类。有一个派生类来声明一个虚拟析构函数吗?

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

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

具体问题:

  1. 1) 和 2) 一样吗?2)是因为它的基础而自动虚拟还是“停止”虚拟?
  2. 如果派生的析构函数无关,可以省略吗?
  3. 声明派生析构函数的最佳实践是什么?声明它是虚拟的、非虚拟的还是尽可能省略它?
4

5 回答 5

98
  1. 是的,它们是一样的。派生类没有声明一些虚拟的东西并不能阻止它成为虚拟的。事实上,如果派生类中的任何方法(包括析构函数)在基类中是虚拟的,则无法阻止它成为虚拟方法。在 >=C++11 中,您可以使用final它来防止它在派生类中被覆盖,但这并不妨碍它成为虚拟的。
  2. 是的,如果派生类中的析构函数无关,可以省略它。它是否是虚拟的并不重要。
  3. 如果可能的话,我会省略它。为了清楚起见,我总是在派生类中使用virtual关键字或虚函数。override人们不应该一直走上继承层次结构来确定一个函数是虚拟的。此外,如果您的类是可复制或可移动的,而无需声明您自己的复制或移动构造函数,则声明任何类型的析构函数(即使您将其定义为default)都将强制您声明复制和移动构造函数和赋值运算符(如果需要)它们作为编译器将不再为您放入它们。

作为第 3 项的一个小点。评论中已经指出,如果未声明析构函数,编译器会生成一个默认的(仍然是虚拟的)。而那个默认的是一个内联函数。

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

我个人的选择仍然是尽可能省略它。在我看来,它会使代码变得混乱,并且编译器有时可以使用默认实现而不是空的实现稍微更有效的事情。但是,您可能会受到一些限制,这使您做出了一个糟糕的选择。

于 2010-02-04T09:04:13.927 回答
2
  1. 与所有方法一样,析构函数是自动虚拟的。你不能阻止一个方法在 C++ 中是虚拟的(如果它已经被声明为虚拟的,也就是说,在 Java 中没有“final”的等价物)
  2. 是的,可以省略。
  3. 如果我打算对这个类进行子类化,我会声明一个虚拟析构函数,无论它是否是另一个类的子类,我也更喜欢继续声明虚拟方法,即使它不是必需的。如果您决定删除继承,这将使子类保持工作。但我想这只是风格问题。
于 2010-02-04T09:06:40.077 回答
1

虚拟成员函数将隐含地使该函数的任何重载成为虚拟的。

所以 1) 中的 virtual 是“可选的”,基类析构函数是 virtual 使得所有子析构函数也都是虚拟的。

于 2010-02-04T09:04:25.287 回答
0

1/ 是 2/ 是的,它将由编译器生成 3/ 是否将其声明为虚拟应该遵循您对覆盖虚拟成员的约定——恕我直言,两种方式都有很好的论据,只需选择一个并遵循它。

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

于 2010-02-04T09:07:55.010 回答
0

虚函数被隐式覆盖。当子类的方法与基类中虚函数的方法签名匹配时,它会被覆盖。这在重构过程中很容易混淆并可能中断,因此自 C++11 以来就有overridefinal关键字来显式标记此行为。有一个相应的警告禁止静默行为,例如-Wsuggest-override在 GCC 中。

SO上有一个相关的问题override和关键字: 'override'关键字只是检查覆盖的虚拟方法吗?.final

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

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

于 2020-02-02T10:07:16.473 回答