虚拟析构函数是继承的吗?

Posted

技术标签:

【中文标题】虚拟析构函数是继承的吗?【英文标题】:Are virtual destructors inherited? 【发布时间】:2011-01-12 23:49:01 【问题描述】:

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

class base 
public:
    virtual ~base () 
;

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

具体问题:

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

【问题讨论】:

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

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

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

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

【讨论】:

我不同意“省略”部分。在标头中声明它并在源中定义它(空体)并不需要太多成本。如果这样做,您可以随时返回并添加一些步骤(记录?),而无需强制您的客户端重新编译。 其实我并没有声明太多的函数内联,甚至没有经典的“访问器”,但是在大公司工作,我们可能有比大多数更高的二进制兼容性限制。跨度> 我刚刚从this talk 了解到,声明虚拟析构函数实际上会导致您的类变得无法移动!因此,无论何时您声明一个虚拟析构函数,如果您想要这些属性,您还必须提供完整的 5 规则。有更多理由在可能的情况下省略。 "另外,如果你的类是可复制的或可移动的,而无需声明自己的副本或移动构造函数,声明任何类型的析构函数(即使你将其定义为默认值)将强制你声明如果需要,请复制和移动构造函数和赋值运算符,因为编译器将不再为您放入它们。”那是错的! en.cppreference.com/w/cpp/language/copy_constructor @Kaiserludi - 我会仔细检查这是否属实并修正我的答案。【参考方案2】:

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

在 SO:Is the 'override' keyword just a check for a overridden virtual method? 上有一个与 overridefinal 关键字相关的问题。

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

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

【讨论】:

【参考方案3】:

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

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

【讨论】:

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

【讨论】:

析构函数不是自动虚拟的,任何其他成员函数也不是。 @Neil;当然不是,我在示例中指的是 the 析构函数(即基类有一个虚拟析构函数),而不是一般的析构函数。这适用于所有方法,而不仅仅是析构函数。 从C++11开始,我们有了final【参考方案5】:

一个虚成员函数将隐含地使这个函数的任何重载成为虚函数。

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

【讨论】:

以上是关于虚拟析构函数是继承的吗?的主要内容,如果未能解决你的问题,请参考以下文章

虚拟继承中的析构函数

析构函数可以是最终的吗?

C++ 中的虚拟默认析构函数

C++中的继承和纯虚函数

构造函数和析构函数能不能被继承

C++中,子类会继承父类的虚函数表!对于父类的析构函数(虚函数) 也会继承吗?