【问题标题】:Virtual destructor use cases虚拟析构函数用例
【发布时间】:2017-03-16 19:14:48
【问题描述】:

我读过一些文章,正如他们所说,主要的虚拟析构函数用例是:

  • 派生类可能具有来自堆的动态数据分配,即“拥有”该数据对象。因此,他们需要在析构函数中进行一些删除例程。通过base类指针删除需要virtual在所有派生类中声明析构函数,直到具有动态数据分配的那些(base类也需要它)

  • 这个类有virtual 方法。但这对我来说还不清楚。仅通过 base 类指针调用 virtual 方法总是会导致最派生实现调用。他们唯一排除该规则的是施工阶段。从字面上看,在这期间,this 对象还不是 派生 类型,即使稍后会是。好的,破坏阶段呢?据我了解,规则是倒序的。无论如何,层次结构中某个类的析构函数被声明为virtual,在每个析构函数期间this指针被用作此类类型,任何派生都已经由于virtual dr而被销毁,或者没有被销毁(并且假设在某些设计中可能没问题)。也许是这样,为什么 d-r 必须是虚拟的? Vtable 将有 derived 类的条目,并且从 d-r 调用某些 base 类中的 virtual 方法会导致 UB?好的,但是这条规则仅适用于此类在 d-r 中调用某些 virtual 方法的情况,并且它们确实在派生类中实现。

我认为,也可能存在没有动态数据分配的情况,在所有层次结构中都没有虚拟方法,但派生的析构函数仍然可以在删除时执行一些关键任务(同步、解锁等)。我们需要基类中的虚拟 d-r。这种情况可能是设计不当的结果。

但是无论如何,某些公共类的开发人员不能100%知道派生类是否会使用d-r中的某些虚拟方法,或者分配动态数据。那么,我是否正确地说,任何未声明为 final 的公共类都必须将 d-r 声明为虚拟?只有final 关键字保证指向该类的任何指针都将始终属于该类型,因此可以安全地以非虚拟方式删除。

【问题讨论】:

  • 没有。类本身在内部做什么或包含什么是完全不相关的。唯一的标准是如何使用它。如果要通过基指针删除派生类对象,则基类必须具有虚拟析构函数。不多也不少。
  • 但是开发者不能确定使用基类指针作为派生类的指针,其他开发者会以这种方式制作和使用。所以,基类开发者应该将 d-r 声明为虚拟的。或者有UB的机会。
  • 我没有听到太多关于开发人员使用std::vector 或实际上 90% 以上的标准库类进行此操作的故事。当它偶尔发生时,开发人员很快就会受到打击,并学会不再这样做。类作者应记录预期用途。它们不需要提供防止非预期使用的保护措施。
  • 好的,我知道了,谢谢!

标签: c++ c++11 undefined-behavior virtual-functions class-hierarchy


【解决方案1】:

如果通过指向基类的指针删除派生对象,则(并且仅在那时)基类析构函数必须是虚拟的。否则为undefined behaviour。没有其他相关规则。

如果类有虚函数,则不会引入任何开销。如果该类没有任何其他虚函数,则基类设计者必须考虑在添加虚析构函数的运行时惩罚与类用户可能尝试通过基类指针。

Here is a link to a similar discussion, with Standard quotes

【讨论】:

  • 好的,谢谢,我现在明白了。所以,我的建议主要是正确的,开发者根据希望用户是不是好用户来决定这个
  • @ЯрикЗюлин 或者相反,用户应该在决定尝试从它派生并使用基类指针之前调查该类是否具有虚拟析构函数(无论如何这可能是一个坏主意,有没有虚函数时没有理由这样做)
  • 如果设计者在删除时使用了基类指针,但有非虚拟的dr——这隐含地说,这个基类根本不应该用作基类,是吗?
  • @ЯрикЗюлин 如果设计器不包含虚拟析构函数,那么通常是的,它不是打算从中派生的。例如std::string 和其他标准容器。不知道你所说的基类指针是什么意思
  • A级{ A级*哥们; A(class A*new_buddy){buddy = new_buddy;} ~A(){delete buddy;} }; B类:A {}; A val = A (new B() );有设计师认为,该好友只能是 A,但用户试图将 B 作为好友
猜你喜欢
  • 2013-07-06
  • 2011-08-12
  • 2012-04-13
  • 2012-04-18
  • 2012-12-20
  • 2014-11-04
  • 1970-01-01
  • 2022-01-01
相关资源
最近更新 更多