![]() |
1
14
你已经发表了一些笼统的声明,我认为大多数务实的程序员都会因为误传或误解而不屑一顾。但是,反虚拟狂热者确实存在,他们的代码对性能和维护也同样有害。 在Java中,缺省情况下所有的东西都是虚拟的。说你不应该过度使用虚函数是很强的。 在C++中,你必须声明一个函数虚函数,但是在适当的时候使用它是完全可以接受的。
很难定义“过度”…当然,“在适当的时候使用虚拟功能”是很好的建议。
设计糟糕的代码很难维护。时期。 如果您是一个库维护人员,调试隐藏在高类层次结构中的代码,那么很难跟踪代码实际执行的位置,如果没有强大的IDE的好处,通常很难判断哪个类重写了行为。它会导致在跟踪继承树的文件之间来回切换。 所以,有一些经验法则,都有例外:
实际情况是,虚拟函数非常有用,这些疑点不太可能来自平衡源——虚拟函数已经被广泛使用了很长时间。比其他语言更新的语言采用它们作为默认语言。 |
![]() |
2
7
虚函数比常规函数稍慢。但这种差别是如此之小,以至于除了最极端的情况外,没有任何区别。 我认为避免使用虚拟函数的最好原因是为了防止接口被滥用。 写类以供扩展是一个好主意,但是有一件事 太开 . 通过仔细规划哪些函数是虚拟的,您可以控制(并保护)类的扩展方式。 当一个类被扩展,从而破坏了基类的契约时,就会出现错误和维护问题。下面是一个例子:
这里,DoubleWidget破坏了父类,因为
现在小部件不会碰到
|
![]() |
3
6
每个依赖项都会增加代码的复杂性,并使维护变得更加困难。当您将函数定义为虚函数时,您将创建类对其他一些代码的依赖关系,这些代码目前可能还不存在。 例如,在C中,您可以很容易地找到foo()的作用——只有一个foo()。在没有虚拟函数的C++中,它稍微复杂一些:你需要探索类和它的基类来找到我们需要的哪一个()。但至少可以提前确定地完成,而不是在运行时。对于虚拟函数,我们无法判断执行的是哪个foo(),因为它可以在一个子类中定义。 (另一件事是你提到的性能问题,由于v-table)。 |
![]() |
4
3
我怀疑你误解了这句话。 过度是一个非常主观的术语,我认为在这种情况下,它意味着“当你不需要它的时候”,而不是当它有用的时候你应该避免它。 根据我的经验,有些学生在学习虚拟函数时,第一次忘记将函数变为虚拟函数而被烧掉, 认为只需将每个函数虚化是明智的 . 由于虚拟函数在每个方法调用上都要付出代价(在C++中,由于单独编译通常无法避免),所以现在基本上为每一个方法调用付费,同时也防止内联。许多教师劝阻学生不要这样做,尽管“过度”一词是一个非常糟糕的选择。 在Java中,一个“虚拟”行为(动态调度)是默认的。然而,JVM可以在运行中对事物进行优化,并且理论上可以在目标身份明确时消除一些虚拟调用。另外,最终类中的最终方法通常也可以在编译时解析为单个目标。 |
![]() |
5
2
在C++中:
现在让我澄清一下:我是 不 说“不要使用虚拟功能”。它们是C++的重要和重要组成部分。只需意识到复杂性的潜在性。 |
![]() |
6
2
我们最近有一个很好的例子,说明了如何滥用虚拟函数会引入错误。 有一个共享库具有消息处理程序:
其目的是您可以从该类继承并将其用于自定义错误处理:
错误处理机制使用
酷,对吧?是的,直到共享库的开发人员更改了基类:
…过载刚刚停止工作。 你看到了吗?基类被更改后,重载从C++视图中停止为重载-它们变成了 新的、其他的、不相关的功能 . 基类的默认实现没有标记为纯虚拟,因此派生类没有被强制重载默认实现。最后,只有在错误处理的情况下才调用该函数,而错误处理不是到处都使用。因此,这个臭虫被悄悄地引进,并在相当长的一段时间里不被注意。 唯一彻底消除它的方法是搜索所有代码库并编辑所有相关的代码片段。 |
![]() |
7
1
我不知道你在哪里读到的,但我不知道这和表演有关。 也许它更多的是“更喜欢关于继承的组成”和如果你的类/方法不是最终的(可能主要是Java在这里)而可能发生的问题,但实际上并不是为了重用而设计的。有很多事情可能真的出错:
综上所述,如果您不为扩展而设计类(提供钩子,记录一些重要的实现内容),那么您根本不应该允许继承,因为这可能导致错误。如果需要的话,也可以很容易地从某个类中删除最后一个修饰符(或者为可重用性重新设计它),但不可能使非最终类(在子类化导致错误的情况下)成为最终类,因为其他类可能已经对它进行了子类化。 也许这真的是关于性能的,那么我至少不谈这个话题。但是如果它不是,那么你有一些很好的理由不让你的类可以扩展,如果你真的不需要它的话。 在BooSCS有效Java中,关于这类信息的更多信息(这篇文章是在我阅读了第16项(“喜欢作文比继承”)和17(“设计和文档继承或其他禁止”)之后的几天写的。 |
![]() |
8
0
我在一个大约7年的时间里,在同一个C++系统上偶尔做顾问,检查大约4-5个程序员的工作。每次我回去,系统都变得越来越糟。在某种程度上,有人决定删除所有的虚拟功能,并用一个非常迟钝的基于工厂/RTTI的系统取代它们,这个系统基本上完成了虚拟功能已经完成的所有工作,但更糟的是,更昂贵的是,成千上万行代码,大量工作,大量测试,…完全和完全毫无意义,明显害怕未知的驱动力。 他们还手工编写了几十个带有错误的复制构造函数,当编译器自动生成它们时,这些构造函数没有错误,在需要手写版本的情况下,大约有三个例外。 道德:不要与语言作斗争。它给你东西:使用它们。 |
![]() |
9
0
为每个类创建虚拟表,其中包含虚拟函数或从包含虚拟函数的类派生。这比通常的空间消耗更多。 编译器需要静默地插入额外的代码,以确保发生后期绑定而不是早期绑定。这比平时要花更多的时间。 |
![]() |
10
0
在爪哇,没有
例如,ApacheSpark2(在JVM上运行)中最大的优化之一是减少虚拟函数分派的数量,以获得更好的性能。 |