1

我希望这个问题不是太含糊,但是来自 java,我想不出任何理由为什么我会在 C++ 中使用非虚拟函数。有没有一个很好的例子来展示 C++ 中非虚拟函数的好处。

4

5 回答 5

4

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

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

于 2012-12-08T19:17:08.683 回答
1

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

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

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

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

于 2012-12-08T19:17:14.450 回答
1

的确,调用虚函数可能会更慢,但并不像大多数 C++ 程序员想象的那么慢。

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

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

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

但根据经验,你总是想使用虚函数的直觉并没有那么糟糕。性能差异明显的时候很少见。

于 2012-12-08T19:27:38.557 回答
1

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

我只能记住what标准异常的析构函数和函数。

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

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

从那以后,我了解到 MFC 不具有代表性,也不能代表一般的 C++ 软件,因此特定于 MFC 的原因并不普遍适用。:-)


虚拟功能的效率成本几乎不存在。:-) 例如,参见国际标准化委员会关于 C++ 性能的技术报告。然而,为派生类提供这种自由是有实际成本的,因为自由意味着责任:任何派生类都必须确保覆盖成员函数尊重基类的约定。

于 2012-12-08T19:29:12.610 回答
0

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

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

于 2012-12-08T19:34:54.547 回答