每个类都应该有一个虚拟析构函数吗?

Posted

技术标签:

【中文标题】每个类都应该有一个虚拟析构函数吗?【英文标题】:Should every class have a virtual destructor? 【发布时间】:2010-09-26 02:41:15 【问题描述】:

Java 和 C# 支持不能用作带有 finalsealed 关键字的基类的类的概念。然而,在 C++ 中,没有很好的方法来防止类派生,这让类的作者陷入两难境地,


编辑:由于 C++11 不再适用,您可以指定一个类为 final


一方面,给对象一个虚拟析构函数意味着它将有一个vtable,因此每个对象为vptr消耗4个(或在64位机器上为8个)额外字节。

另一方面,如果后来有人从这个类派生并通过指向基类的指针删除派生类,则程序将定义不正确(由于没有虚拟析构函数),坦率地说,为指针优化每个对象都是荒谬的。

在gripping hand 上,有一个虚拟析构函数(可以说)表明这种类型是为了多态使用。

有些人认为你需要一个明确的理由不使用虚拟析构函数(就像this question 的潜台词)而其他人说你应该只在你有理由相信你的类是从,怎么看?

【问题讨论】:

已经有问题询问利弊 - 这是重复的,还是打算作为民意调查?如果是后者,也许您应该为投票创建“是”和“否”答案,然后关闭问题?我认为这是对 SO 实施多项选择投票的推荐方式。 重复:***.com/questions/270917/…,***.com/questions/300986/… “坦率地说,为每个对象优化一个指针是荒谬的。” - 对于小对象来说并不荒谬。 C++0x 正在添加一个容器 forward_list,正是因为有时每个对象一个指针的开销太大——从空间和时间要求来看。 @onebyone,这个问题不是你列出的第一个问题的重复,它是特定于 abstract 类的,我在我的问题中引用了第二个问题,我没有认为这是重复的,因为这个问题强烈偏向于虚拟 dtors 我想要一个公开讨论。 @Kyralessa,极客必须做极客必须做的事情:) 【参考方案1】:

对于一般性问题,我会“不”。不是每个类都需要一个。如果您知道永远不应该继承该类,那么就没有必要产生较小的开销。但如果有机会,为了安全起见,放一个进去。

【讨论】:

【参考方案2】:

不!仅当通过基类指针删除派生类的对象时,才使用虚拟析构函数。如果您的类不打算在这种情况下用作基础,请不要将析构函数设为虚拟 - 您会发送错误消息。

【讨论】:

那么您如何预测您的代码有用的每一个实例?也许在某些情况下,您的类(本应由您以非多态方式使用)仅在某些非常特定的实例中用作基类。【参考方案3】:

每个抽象类都应该有一个,

受保护的析构函数,或者, 虚拟析构函数。

如果您有一个公共的非虚拟析构函数,那就不好了,因为它允许用户通过该指针删除派生对象。众所周知,这是未定义的行为。

对于抽象类,您已经需要对象中的虚拟表指针,因此使析构函数 virtual 不会(据我所知)在空间或运行时性能方面有很高的成本.它的好处是派生类自动拥有其析构函数virtual(请参阅@Aconcagua 的评论)。当然,你也可以为这种情况制作析构函数protected virtual

对于不打算通过指向它的指针删除的非抽象类,我认为没有充分的理由使用虚拟析构函数。它会浪费资源,但更重要的是它会给用户一个错误的提示。想想给std::iterator一个虚拟析构函数会有什么奇怪的意义。

【讨论】:

或 struct tm from ,它将不再是 POD,因此不再与 C 调用约定兼容。 很好的答案,litb。我现在要去更改我所有的代码。 后期添加,我知道——但我不同意受保护的析构函数。有人可能会忽略将派生类的公共析构函数再次设为虚拟的必要性,而其他人可能会假设已经虚拟析构函数从派生中派生——并(非法)通过指向子类的指针删除孙子……当然,第二人称然后犯了错误,但如果析构函数在第一个基类中已经是虚拟的,则可以避免,所以从安全的角度来看,我只认为第二个选项有效(当然,仅限抽象类)。 @Aconcagua 我对第二作者的规则是:每个非抽象可实例化类都应该有一个虚拟公共析构函数,或者非虚拟公共析构函数并声明为final 你给每个抽象类一个虚拟析构函数的想法听起来很有趣。也许我应该采用它。我的std::iterator 的例子有点差,因为std::iterator 根本不是抽象的。我会修改的。【参考方案4】:

我要补充一点,当我在父类或子类中忘记了一个虚拟时,我曾一度为没有调用析构函数而挠头。我想我现在知道要找那个了。 :)

有人可能会争辩说,有时父类在其析构函数中做了一些子类不应该做的事情……但这可能表明你的继承结构有问题。

【讨论】:

【参考方案5】:

问题是真的,你想执行关于你的类应该如何使用的规则吗?为什么? 如果一个类没有虚拟析构函数,那么使用该类的任何人都知道它不打算派生自该类,以及如果您尝试使用它会受到哪些限制。这还不够好吗?

或者,如果有人做你没有预料到的事情,你是否需要编译器抛出一个硬错误?

如果您打算让人们从中派生,请给该类一个虚拟析构函数。否则不要,并假设使用您的代码的任何人都足够聪明,可以正确使用您的代码。

【讨论】:

@jalf: 如果他们从中派生并且想以多态方式使用它/【参考方案6】:

查看this article from Herb Sutter:

准则 #4:基类析构函数应该是公共的和虚拟的,或者受保护的和非虚拟的。

【讨论】:

请注意,他说的是应该是 base 类的类,这个问题专门针对不是设计为基类的类。 @Paolo Tedesco “应该是公共的和虚拟的,或者是受保护的和非虚拟的**或公共的和非虚拟的并标记为 final 关键字。”我说的对吗? @John 这个答案比 c++ 11 早 :)【参考方案7】:

当基类包含至少一个纯虚函数时,基类变为抽象类。如果 Base 没有虚拟析构函数而 Derived(从 Base 派生)有,那么您可以通过 Derived 对象指针而不是通过 Base 对象指针安全地销毁 Derived 对象。

【讨论】:

以上是关于每个类都应该有一个虚拟析构函数吗?的主要内容,如果未能解决你的问题,请参考以下文章

静态对象成员会在所属类的析构函数被调用时自动析构吗?

模板化的指针类可以有虚拟析构函数吗?

在没有虚拟析构函数的情况下删除基类指针会导致内存泄漏吗?

CRT 虚拟析构函数

直接调用(虚拟)析构函数是不是有效?

Swift - 43 - 继承, 多态, 析构函数