【问题标题】:C++ : implications of making a method virtualC++:使方法虚拟化的含义
【发布时间】:2009-11-30 18:33:59
【问题描述】:

应该是新手问题...

我想扩展现有类 A 中的现有代码以覆盖现有方法 A::f()。

所以现在我想创建类 B 来覆盖 f(),因为我不想只更改 A::f(),因为其他代码依赖于它。

为此,我相信我需要将 A::f() 更改为虚拟方法。

我的问题是除了允许动态调用方法(使用 B 的实现而不是 A 的实现)之外,使方法虚拟化还有其他含义吗?我是否打破了某种良好的编程习惯?这会影响任何其他尝试使用 A::f() 的代码吗?

请告诉我。

谢谢, jbu

编辑:我的问题更像是让别人的方法虚拟化有什么问题吗?即使您没有更改其他人的实现,您仍然必须进入某人的现有代码并更改声明。

【问题讨论】:

  • 您不能只在第三方组件的标头中添加 virtual 并使其工作 - 您还需要重新编译该组件。
  • 很多人给了你有用的答案,而你却没有费心去标记它们,或者接受一个答案。这不好。

标签: c++ virtual overriding implementation


【解决方案1】:

如果您在基类中使函数为虚拟,则从它派生的任何东西也将使其为虚拟。

一旦是虚拟的,如果你创建一个A的实例,那么它仍然会调用A::f

如果您创建 B 的实例并将其存储在 A* 类型的指针中。然后你调用A*::->f,然后它会调用BB::f

至于副作用,除了轻微(不明显)的性能损失外,可能不会有任何副作用。

还有一个非常小的副作用,可能有一个类 C 也是从 A 派生的,它可能实现 C::f,并期望如果调用了 A*::->f,那么它期望 A::f叫做。但这并不常见。

但更有可能的是,如果C 存在,那么它根本不实现C::f,在这种情况下一切都很好。


但要小心,如果您使用的是已经编译的库并且正在修改它的头文件,那么您期望的工作可能不会。您将需要重新编译头文件和源文件。

您可以考虑执行以下操作以避免副作用:

  1. 创建一个派生自A 的类型A2 并使其成为f 虚拟
    • 使用A2 类型的指针而不是A
    • A2 类型派生B
    • 这样,任何使用 A 的东西都可以保证以同样的方式工作

根据您的需要,您还可以使用has-a 关系而不是is-a

【讨论】:

  • 感谢您的回复。我想我的问题更多的是将别人的方法设为虚拟有什么问题吗?
【解决方案2】:

每次调用虚函数时,vtable 查找都会对性能造成很小的隐含损失。如果它不是虚拟的,则函数调用是直接的,因为代码位置在编译时是已知的。而在运行时,必须从您正在调用的对象的 vtable 中引用虚函数地址。

【讨论】:

  • 有人曾经评论过,虚拟函数对性能的影响就像理发对体重的影响。
【解决方案3】:

为此,我需要将 A::f() 更改为 我相信是一种虚拟方法。

不,您无需将其更改为虚拟方法即可覆盖它。但是,如果您使用的是多态性,则需要使用,即如果您有很多不同的类派生自 A,但存储为指向 A 的指针。

由于 vtable 的存在,虚函数也会产生内存开销(除了 spoulson 提到的)

【讨论】:

    【解决方案4】:

    还有其他方法可以实现您的目标。 B 成为 A 有意义吗?例如,猫是动物是有意义的,但猫是狗是没有意义的。如果 A 和 B 是相关的,也许 A 和 B 都应该派生自一个基类。

    是否有您可以分解的常见功能?在我看来,您永远不会多态地使用这些类,而只想要功能。我建议你去掉这个通用功能,然后创建两个单独的类。

    至于成本,如果您直接使用 A 和 B,编译将绕过任何虚拟调度并直接进入函数调用,就好像它们从来都不是虚拟的一样。如果您将 B 传递到需要 `A1(作为引用或指针)的地方,那么它将不得不调度。

    【讨论】:

      【解决方案5】:

      在谈到虚拟方法时,有 2 个性能问题。

      • vtable 调度,没什么好担心的
      • 虚拟函数永远不会内联,这可能比前一个更糟糕,函数内联在某些情况下可以真正加快速度,而虚拟函数永远不会发生这种情况。

      【讨论】:

        【解决方案6】:

        更改别人的代码是否符合规定完全取决于当地的风俗习惯。这不是我们可以为您解答的问题。

        下一个问题是该类是否被设计为继承自。在许多情况下,类不是,并且在不改变其他方面的情况下将它们更改为有用的基类可能会很棘手。非基类很可能拥有除公共函数之外的所有内容,因此如果您需要访问 B 中的更多内部,则必须对 A 进行更多修改。

        如果您打算使用 B 类而不是 A 类,那么您可以直接覆盖该函数而不使其成为虚拟函数。如果您要创建 B 类的对象并将它们作为指向 A 的指针引用,那么您确实需要将 f() 设为虚拟。您还应该将析构函数设为虚拟。

        【讨论】:

          【解决方案7】:

          在应得的地方使用虚拟方法是一种很好的编程习惯。虚拟方法对 C++ 类的合理程度有很多影响。

          没有虚函数,您无法在 C++ 中创建接口。接口是具有所有未定义虚函数的类。

          但有时使用虚方法并不好。使用虚拟方法来更改对象的功能并不总是有意义,因为它意味着子类化。通常,您可以使用函数对象或函数指针来更改功能。

          如前所述,虚函数会创建一个表,运行程序将引用该表来检查要使用的函数。

          C++ 有很多陷阱,这就是为什么人们需要非常清楚自己想要做什么以及最好的方法是什么。与运行时动态 OO 编程语言(如 Java 或 C#)相比,做某事的方法并不多。随着代码的发展,有些方法要么完全错误,要么最终导致未定义的行为。

          既然您提出了一个非常好的问题 :D,我建议您购买 Scott Myer 的书:有效的 C++ 和 Bjarne Stroustrup 的书:C++ 编程语言。这些将教您 C++ 中 OO 的精妙之处,尤其是何时使用什么功能。

          【讨论】:

            【解决方案8】:

            如果这是该类将拥有的第一个虚拟方法,那么您将使其不再是 POD。这可能会破坏事情,尽管这种可能性很小。

            POD:http://en.wikipedia.org/wiki/Plain_old_data_structures

            【讨论】:

              猜你喜欢
              • 2015-09-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2012-11-30
              • 2016-09-28
              相关资源
              最近更新 更多