2

应该是新手问题...

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

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

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

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

请告诉我。

谢谢,jbu

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

4

8 回答 8

6

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

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

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

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

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

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


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

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

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

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

于 2009-11-30T18:37:01.053 回答
4

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

于 2009-11-30T18:40:10.653 回答
4

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

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

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

于 2009-11-30T18:44:05.310 回答
2

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

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

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

于 2009-11-30T18:44:03.567 回答
2

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

  • vtable 调度,没什么好担心的
  • 虚函数永远不会内联,这可能比前一个更糟糕,函数内联在某些情况下可以真正加快速度,它永远不会发生在虚函数上。
于 2009-11-30T18:50:38.870 回答
2

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

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

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

于 2009-11-30T18:52:07.820 回答
1

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

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

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

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

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

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

于 2009-11-30T18:49:28.450 回答
0

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

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

于 2009-11-30T19:31:41.423 回答