何时适合使用虚拟方法

When is it appropriate to use virtual methods?

本文关键字:虚拟 方法 何时适      更新时间:2023-10-16

我知道虚拟方法允许派生类重写从基类继承的方法。 何时适合/不适宜使用虚拟方法? 并不总是知道一个类是否会被子类化。 一切都应该虚拟化,只是"以防万一"吗? 还是会导致大量开销?

首先是一个有点迂腐的评论 - 在标准C++我们称它们为成员函数,而不是方法,尽管这两个术语是等价的。

我认为有两个原因不使成员函数虚拟化。

  • "YAGNI" - "你不需要它"。 如果不确定类是否派生自,请假定它不会派生,并且不要使成员函数成为虚拟函数。 顺便说一下,没有什么比非虚拟析构函数更能说"不要从我那里派生"了(编辑:在 C++11 及更高版本中,你有 final 关键字],这甚至更好)。 这也与意图有关。 如果您不打算多态地使用该类,请不要创建任何虚拟内容。 如果你任意地将成员虚拟化,你就是在招致对利斯科夫替代原则的滥用,而这些类别的错误是痛苦的追踪和解决。
  • 性能/内存占用。 没有虚拟成员函数的类不需要 VTable(虚拟表,用于通过基类指针重定向多态调用),因此(可能)占用更少的内存空间。此外,直接成员函数调用(可能)比虚拟成员函数调用更快。不要通过先发制人地使成员函数虚拟化来过早地悲观您的类。

当你设计一个类时,你应该有一个很好的主意,它是否表示一个接口(在这种情况下,你标记适当的可重写方法和析构函数虚拟),或者它打算按原样使用,可能与其他对象组合或组合。

换句话说,你对这门课的意图应该是你的向导。将所有内容设为虚拟通常是矫枉过正的,有时还会误导哪些方法旨在支持运行时多态性。

这是一个棘手的问题。但是有一些指导方针/经验法则需要遵循。

  1. 只要你不需要从类派生,那么就不要写任何virtual方法,一旦需要派生,只做virtual那些你需要自定义的方法在子类中。
  2. 如果一个类有一个virtual方法,那么析构函数应该是virtual的(讨论结束)。
  3. 尝试遵循 NVI(非虚拟接口)习惯用法,使virtual方法非公开,并提供负责评估前置和后置条件的公共包装器,以便派生类不会意外破坏它们。

我认为这些都很简单。我绝对让 ABI 部分的反射消失了,它仅在交付 DLL 时才有用。

如果您的代码遵循特定的设计模式,那么您的选择应反映 DP 自己的原则。例如,如果您正在编写装饰器模式,则应该是虚拟的函数是属于组件接口的函数。

否则,我想遵循一种进化的方法,IOW 我没有虚拟方法,直到我看到层次结构试图从您的代码中出现。

例如,

Java中的成员函数是100%虚拟的。在C++,它被视为代码大小/函数调用时间的损失。此外,非虚函数保证函数实现始终相同(使用基类对象/引用)。斯科特·迈耶斯(Scott Meyers)在"有效C++"中对此进行了更详细的讨论。

我最常使用的理智测试是 - 如果我正在定义的类将来派生自,行为(函数)是保持不变还是需要重新定义。如果是,该功能是虚拟的有力竞争者,如果不是,那么不,如果我不知道 - 我可能需要研究问题域以更好地了解我计划实现的行为。大多数问题域给了我答案 - 在没有的情况下,行为通常是非关键的。

我想快速确定的一种可能方法是考虑是否要处理一堆用于执行相同任务的类似类,更改是您执行这些任务的方式

一个微不足道的例子是计算各种几何图形的区域问题。您需要正方形,圆形,矩形,三角形等面积,这里唯一改变的是用于计算面积的数学公式(方式)。因此,最好让这些形状中的每一个继承自公共基类,并在基类中添加一个返回该区域的虚拟方法(然后可以使用相应的数学公式在每个子级中实现)。

将所有内容设为虚拟"以防万一"将使对象占用更多内存。此外,调用虚函数时会产生少量(但非零)开销。因此,恕我直言,当性能/内存约束很重要时,将所有内容"以防万一"虚拟化将是坏主意(这基本上意味着在您编写的每个现实世界程序中始终如此)。

但是,根据要求的详细说明程度以及预期的代码更改频率,这再次值得商榷。例如,在一个快速而肮脏的工具或初始原型中,几个额外的内存字节和几毫秒的损失时间并没有多大意义,为了灵活性,拥有一堆(不必要的)虚拟功能是可以的。

我的观点是,如果要使用父类指针指向子类实例并使用它们的方法,那么您应该使用虚拟方法。

虚拟方法是实现多态性的一种方法。当您想要在更抽象的级别定义某些操作时,会使用它们,以便由于它太笼统而无法实际实现。只有在派生类中,您才能告知如何执行该操作。但是,通过定义虚拟方法,您可以创建一个需求,这增加了类层次结构的刚性。这可以是可取的,也可以不建议,这取决于你想获得什么,以及你自己的口味。

看看设计模式。如果您的代码/设计是这些或类似的代码/设计之一,请使用虚拟功能。否则,请尝试此操作