什么时候适合使用虚方法?
Posted
技术标签:
【中文标题】什么时候适合使用虚方法?【英文标题】:When is it appropriate to use virtual methods? 【发布时间】:2011-08-28 21:23:54 【问题描述】:我了解虚拟方法允许派生类覆盖从基类继承的方法。什么时候使用虚拟方法合适/不合适?并不总是知道一个类是否会被子分类。一切都应该虚拟化,以防万一吗?还是会导致大量开销?
【问题讨论】:
【参考方案1】:首先有点迂腐的评论 - 在 C++ 标准中,我们称它们为成员函数,而不是方法,尽管这两个术语是等价的。
我看到了两个不将成员函数设为虚拟的原因。
“YAGNI”-“你不需要它”。如果您不确定某个类是否会派生,请假设它不会并且不要将成员函数设为虚拟。顺便说一句,没有什么比非虚拟析构函数更能说明“不要从我那里派生”的了(编辑:在 C++11 及更高版本中,你有final
关键字],它甚至更好)。这也与意图有关。如果您不打算以多态方式使用该类,请不要将任何东西设为虚拟。如果您任意将成员设置为虚拟成员,您就是在招致对 Liskov Substitution Principle 的滥用,而且这类错误很难追踪和解决。
性能/内存占用。没有虚拟成员函数的类不需要 VTable(虚拟表,用于通过基类指针重定向多态调用),因此(可能)占用更少的内存空间。此外,直接成员函数调用(可能)比虚拟成员函数调用更快。
不要通过先发制人地将成员函数设为虚拟来过早地悲观您的类。
【讨论】:
【参考方案2】:当您设计一个类时,您应该非常清楚它是否代表一个接口(在这种情况下,您标记适当的可覆盖方法和虚拟析构函数)或者它打算按原样使用,可能是组合或组合与其他对象。
换句话说,您对课程的意图应该是您的指导。将所有东西都虚拟化通常是矫枉过正,有时会误导有关哪些方法旨在支持运行时多态性。
【讨论】:
【参考方案3】:这是一个棘手的问题。但是有一些指导方针/经验法则可以遵循。
-
只要你不需要从一个类派生,那么就不要写任何
virtual
的方法,一旦你需要派生,只需在子类中制作virtual
那些你需要自定义的方法。
如果一个类有一个virtual
方法,那么析构函数应该是virtual
(讨论结束)。
尝试遵循 NVI(非虚拟接口)习惯用法,将 virtual
方法设为非公开,并提供负责评估前置条件和后置条件的公共包装器,以免派生类意外破坏它们。
我认为这些很简单。我绝对让 ABI 部分的反射消失了,它只在交付 DLL 时有用。
【讨论】:
【参考方案4】:如果您的代码遵循特定的设计模式,那么您的选择应该反映 DP 自己的原则。例如,如果您正在编写Decorator pattern,则应该是虚拟的函数是属于 Component 接口的函数。
否则,我想遵循一种进化方法,IOW 我没有虚拟方法,直到我看到一个层次结构试图从你的代码中出现。
【讨论】:
【参考方案5】:例如,Java 中的成员函数是 100% 虚拟的。在 C++ 中,它被视为代码大小/函数调用时间损失。此外,非虚函数保证函数实现始终相同(使用基类对象/引用)。 Scott Meyers 在“Effective C++”中更详细地讨论了它。
【讨论】:
非虚函数“保持不变”这一事实对于调用它的其他函数很重要。当涉及到虚函数时,要正确调用 调用 函数要困难得多。【参考方案6】:我主要使用的健全性测试是 - 如果我定义的类是从未来派生的,行为(功能)是否保持不变或是否需要重新定义。如果是的话,该功能是虚拟的有力竞争者,如果不是,那么没有,如果我不知道 - 我可能需要研究问题域以更好地理解我计划实现的行为。大多数问题域给了我答案 - 在它没有的情况下,行为通常是非关键的。
【讨论】:
【参考方案7】:我想快速确定的一种可能方法是考虑是否要处理一堆相似的类,这些类将用于执行相同的任务,而变化是方式 em> 你去做那些任务。
一个简单的例子是计算各种几何图形的面积问题。您需要正方形、圆形、矩形、三角形等的面积,而这里唯一改变的是您用来计算面积的数学公式(方式)。因此,让这些形状中的每一个都从一个公共基类继承并在返回该区域的基类中添加一个虚拟方法是一个不错的决定(然后您可以使用相应的数学公式在每个孩子中实现) .
将所有东西都虚拟化以“以防万一”将使您的对象占用更多内存。此外,调用虚函数时会有少量(但非零)开销。因此,恕我直言,当性能/内存限制很重要时(这基本上意味着在您编写的每个实际程序中),将所有东西都“以防万一”虚拟化是个坏主意。
但是,这又是值得商榷的,这取决于需求的清晰程度以及预期的代码更改频率。例如,在一个快速而肮脏的工具或初始原型中,一些额外的内存字节和几毫秒的损失时间并没有多大意义,为了起见,拥有一堆(不必要的)虚函数是可以的灵活性。
【讨论】:
【参考方案8】:我的观点是,如果你想使用父类指针指向子类实例并使用它们的方法,那么你应该使用虚拟方法。
【讨论】:
虽然如此,但我认为这并不能真正回答“我怎么知道我需要多态性?”这个更大的问题【参考方案9】:虚拟方法是实现多态性的一种方式。当您想在更抽象的级别定义某些操作时使用它们,因为它太笼统而无法实际实现。只有在派生类中,您才能知道如何执行该操作。但是通过定义虚拟方法,您创建了一个需求,这增加了类层次结构的刚性。这是否可取,这取决于您想要获得什么,以及您自己的品味。
【讨论】:
【参考方案10】:看看Design Patterns。如果您的代码/设计是其中之一或类似的,请使用虚拟功能。否则,试试this
【讨论】:
-1:想要实现 GoF 设计模式和需要虚函数之间没有因果关系。以上是关于什么时候适合使用虚方法?的主要内容,如果未能解决你的问题,请参考以下文章
delphi中覆盖override父类的静态方法和虚函数有啥不同?