为什么在C++中,你必须把“virtual”放在基类中而不是只在子类中使用“override”?

4
标题已经说明了一切。为什么需要在基类中定义一个可能会被重写的函数?
这样,如果您想从基类派生出一个行为不同的版本,则必须修改基类,并且您没有考虑在基类中标记该特定函数为虚函数。
据我所知,在Java中,您可以通过使用override来解决此类抽象定义(尽管我目前只对Java略有了解),这对我来说似乎更自然。C++中的这种扭曲是为了强制程序员提前思考,还是由于某种技术原因造成的?

4
在Java中,所有的函数都是虚拟的,在C++中则不是,除非你希望它们是虚拟的。 - tkausl
2个回答

6

这样做是为了让成员函数是否为虚函数的决策留给基类。

决定将函数设置为虚函数会对设计产生影响:如果你将一个函数设置为虚函数,你必须考虑它可能会有不同的行为。相反,你可以指望非虚函数保持不变。

允许override从外部强制虚拟性可能会破坏函数编写者的假设。此外,它会使分离编译变得复杂,因为C++编译器必须为虚函数做额外的事情,而非虚函数则不需要,例如查找vtable或其他用于实现虚拟分派的机制的有效地址。

相比之下,在Java中,所有公共和受保护的实例方法都是虚拟的,除非你将它们设置为final。C++没有采取这种方式,以便让类设计完全没有虚函数。Java没有这个选项,因为所有类都继承自Object,而C++则允许你使你的类尽可能轻量级。


此外,函数默认不使用virtual的主要原因是效率 - 非虚函数不需要虚表查找,并且可以被编译器内联。Java并不关心这一点,但C++很在意 :) - gflegar
@GoranFlegar 对的。我在最后一句话中提到了“轻量级”类,试图暗示这一点。总的来说,默认提供虚拟功能会违背C++设计原则之一,即“你不用为你不使用的东西付费”。 - Sergey Kalinichenko
1
Java的编译模型也有相当大的影响。Java可以在没有虚拟调用(并进行内联)的情况下编译代码,稍后如果加载了具有覆盖的派生类,则重新编译基本类代码。C++编译器会进行一些去虚拟化的处理,但通常不像Java JITs那样进行推测性的操作。 - Ben Voigt
1
另外,为了避免可能的Java vs C++讨论以及哪个更“好”的问题 - 我的评论有些草率,一些喜欢Java的人可能会觉得冒犯。冒犯任何人都不是我的意图。每种语言都有其优缺点:C++具有终极效率,但与Java相比,你需要付出更高的复杂性和可维护性的代价。 - gflegar
@ Goran Flegar。你走在正确的道路上,但这与内联无关(这是编译器的事情),而与链接有关。您放在头文件中的代码可能会被内联,也可能不会被内联,非虚函数将被调用。通过查找表(称为v-table)调用虚函数。这是编译器进行调用、链接器知道要调用什么以及只有在运行时才知道要调用的地址之间的区别。 - Tiger4Hire
显示剩余2条评论

0
如果元类进入C++20,您可以随时使用"接口"(观看视频)。

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