不使用虚函数的动机

时间:2012-12-08 19:04:12

标签: c++ inheritance virtual

我希望这个问题不是太模糊,但是来自java,我想不出为什么我会在C ++中使用非虚函数。 有一个很好的例子可以证明C ++中非虚函数的好处。

5 个答案:

答案 0 :(得分:4)

虚拟函数具有与之关联的运行时成本。它们在运行时调度,因此调用较慢。它们类似于通过函数指针调用常规函数,其中地址是在运行时根据对象的实际类型确定的。这导致了开销。

C ++设计决策之一一直是您不应该为不需要的东西付费。相比之下,Java并不关心这种低级优化。

答案 1 :(得分:1)

嗯,C ++语言所依据的原则之一是你不应该为你不使用的东西买单。

虚函数调用比非虚函数调用更昂贵,因为在典型的实现中,它通过两个(或三个)附加级别的间接来实现。虚拟呼叫无法内联,这意味着由于我们必须调用一个完整的功能,费用可能会增长得更高。

向类添加虚函数使其具有多态性,从而在该类的对象内创建一些不可见的内部结构。这些结构会产生额外的家庭开支,并妨碍对班级物体的低级处理。

最后,将功能分为虚拟和非虚拟(即可覆盖和不可覆盖的功能)是您的设计问题。无条件地使我们类中的所有函数在派生类中都可以覆盖,这没有任何意义。

答案 2 :(得分:1)

确实,调用虚函数可能比较慢,但并不像大多数C ++程序员所想的那么慢。

现代CPU在分支预测方面已经相当不错。如果每次执行对虚函数的特定调用时,实际上都在调用相同的实现,CPU会在计算出地址之前计算出来并开始“猜测”(推测性地执行)调用。这通常可以完全隐藏虚拟呼叫的成本,使其与非虚拟呼叫一样快。 (如果您对此表示怀疑,请在当前一代处理器上亲自尝试。)

如果您没有调用相同的实现,那么您实际上是依赖于虚拟调度,因此无论如何都无法直接用非虚函数替换它。

唯一常见的例外是内联函数,编译器可以在调用者和被调用者之间执行常量传播,CSE等。显然,如果它在编译时不知道调用的目的地,它就不能这样做。

但是根据经验,你总是想要使用虚拟功能的本能并不是那么糟糕。表现差异明显的时间很少见。

答案 3 :(得分:1)

标准库中很少有成员函数是虚拟的。

Offhand我只记得标准例外的析构函数和what函数。

截至2012年,拥有虚拟成员函数的唯一理由是支持在派生类中覆盖该成员函数,即自定义点,并且通常可以通过其他方式实现(例如参数化,模板化)。

但是,我可以记得,就像15年前一样,对微软MFC类框架的设计感到非常沮丧。我希望每个成员函数都是虚拟的,以便能够覆盖功能,以便能够更轻松地调试事物,作为替代不存在或非常低质量的文档。因此,我认为虚拟应该是默认的,也是在其他软件中。

我已经理解MFC不具代表性,并且一般不代表C ++软件,因此MFC特定的原因一般不适用。 : - )


虚拟功能的效率成本几乎不存在。 :-)参见国际标准化委员会Technical Report on C++ Performance。但是,为派生类提供这种自由是有实际成本的,因为自由意味着责任:任何派生类都必须确保覆盖成员函数尊重基类的契约。

答案 4 :(得分:0)

C ++既快又像C一样,支持OO和泛型编程(模板)。为了实现这两个目标,默认情况下C ++成员函数不能被无效,除非您将它们标记为虚拟,在这种情况下虚拟表开始营业。因此,您可以在不需要时构建不涉及虚函数的类。

虽然效率不如非虚拟调用,但使用虚拟表的虚拟函数调用速度非常快。您可能会注意到仅在紧密循环中的不同之处除了调用成员函数之外什么都不做。所以Java方式 - 所有成员都是“虚拟的” - 确实是更实用的IMO。