公有非虚析构函数的类应该标记为“final”吗?

11
为了让问题重新打开,请投票者帮助我改进这个问题:如何改进这个问题以便重新打开? Herb Sutter在文章中写道

一个基类析构函数应该是public和virtual,或protected和nonvirtual。

根据这个准则,如果你有一个public non-virtual析构函数的类,则该类不应作为基类。 那么为什么不标记为final来加强这一点呢?
但是Sutter也写道了以下内容,暗示final不需要使用:

关于“final的用途较少”-确实是这样。我不知道有多少种情况可以使用它,在标准化期间,Bjarne反复要求列出解决的问题和应该使用它的模式, 我不记得有任何重大的问题或突出的模式。

另一段相关引言表明,现在有了final,应该使用它,这段引言来自于Scott Meyer的Effective C++,第7项:

如果你曾被诱惑继承标准容器或任何具有non-virtual析构函数的其他类,请抵制这种诱惑!(不幸的是,C++没有类似于Java的final classes或C#的sealed classes防止派生的机制。)

另一个数据点是,标准库中没有带“final”标记的类型,但原因似乎是为了避免破坏代码。

这里有一个类似的问题,但不完全相同,因为它缺少"protected, nonvirtual"选项:默认使类成为`final`或给它们一个虚拟析构函数?


只有在尝试通过基类指针删除派生类对象时,您才会遇到问题。如果这在您的应用程序中没有发生 - 可能是因为您没有动态分配对象 - 那就没关系了。 - Bo Persson
2个回答

8
根据该指南,如果您有一个具有公共非虚析构函数的类,则不应将该类用作基类。为什么不将其标记为final以强制执行呢?因为它是一个适用于某些情况的指南,但并非全部情况,所以为什么要“强制执行”呢?禁止继承没有通过虚函数调用实现动态多态性的情况是可以的,但这并不是我们使用继承的唯一场景。C++是多范式的,开始强制执行适用于仅适用于某个用例子集的狭窄方法是没有意义的。据我所知,您的建议本质上归结为除非人们还使用动态多态性,否则禁止使用继承。

1
所有的流都有虚析构函数,而流缓冲区具有大量的虚函数。不确定这是个好的例子。 - T.C.
你的最后一句话并不完全准确,因为它省略了“受保护的非虚析构函数”的选项。 - Jon

3

我通常将类声明为final,除非它们是

  • 旨在用作基类或
  • POD类型。

我认为明确地为继承设计是一件好事(毕竟应该谨慎使用)。如果我没有将一个类设计为基类,我会通过声明其为final来记录此信息。如果以后发现从该类派生会很有用,则需要去掉final,同时检查满足将其作为可行基类的其他条件也是一个好机会。

我通常不会将POD类型声明为final,因为我认为这样做没有任何好处,并且从它们派生有时会利用空基类优化。


你是在描述内部使用的代码,还是个人代码库或某种发布的库?如果是后者,谁能说其他人不会发现继承你的类很有用呢? - einpoklum
我主要是在谈论我有控制权的代码。如果我要设计一个库,我会三思而后行,是否应该从我的公共类型派生出去。这可能会导致我设计更多的继承类,但是是的,我会将剩余的类型声明为“final”。 - 5gon12eder
1
@einpoklum 他们可以而且很可能应该使用组合。 - KABoissonneault
@KABoissonneault,这并非总是可能的。例如,一些std::tuple实现使用内部模板“叶子”类,这些类从元组的模板参数继承以获得空基类优化。我怀疑使用这样的元组与标记为final的类会导致编译错误(编辑:除非它们使用std::is_final特性避免继承,这是他们应该做的),但您无法通过组合获得EBO的好处。 - Caninonos
@Caninonos 这就是我不声明POD类型为“final”的理由。但非POD类型很少会为空。 - 5gon12eder
@5gon12eder:是的,我承认,在那种特定情况下这样做很愚蠢(一个空类不能有任何虚成员,因为vtable不存在,更不用说属性的缺失了,而声明这样的类为final是...奇怪的)。但是,我仍然同意einpoklum的观点,你不应该强加设计决策。 - Caninonos

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