您的问题主要集中在调用虚拟基类的 常规 函数,而不是虚拟基类(A 类)的 virtual 函数(远)更有趣的情况在您的示例中)-但是,是的,可能会产生成本。当然,一切都取决于编译器。
当编译器编译 A::foo 时,它假定“this”指向 A 的数据成员在内存中的起始位置。此时,编译器可能不知道类 A 将是任何其他类的虚拟基。但它很乐意生成代码。
现在,当编译器编译 B 时,实际上不会有任何变化,因为虽然 A 是一个虚拟基类,但它仍然是单继承,在典型情况下,编译器会通过放置类 A 的数据来布局类 B成员紧随其后的是 B 类的数据成员——因此 B * 可以立即转换为 A * 而值没有任何变化,因此不需要进行任何调整。编译器可以使用相同的“this”指针(即使它是 B * 类型)调用 A::foo,并且没有任何危害。
C 类也是同样的情况——它仍然是单继承,典型的编译器会将 A 的数据成员紧跟在 C 的数据成员之后,因此 C * 可以立即转换为 A * 而不会改变任何值。因此,编译器可以简单地使用相同的“this”指针(即使它是 C* 类型)调用 A::foo,并且没有任何危害。
但是,D 类的情况完全不同。D 类的布局通常是 A 类的数据成员,然后是 B 类的数据成员,然后是 C 类的数据成员,然后是 D 类的数据成员。
使用典型的布局,一个 D * 可以立即转换为一个 A *,因此对 A::foo 没有任何惩罚——编译器可以调用它为 A::foo 生成的相同例程,而无需对“这个”,一切都很好。
但是,如果编译器需要调用诸如 C::other_member_func 这样的成员函数,即使 C::other_member_func 是非虚函数,情况也会发生变化。原因是当编译器为 C::other_member_func 编写代码时,它假设“this”指针引用的数据布局是 A 的数据成员紧跟 C 的数据成员。但对于 D 的实例,情况并非如此。编译器可能需要重写并创建一个(非虚拟的)D::other_member_func,只是为了处理类实例内存布局的差异。
请注意,在使用多重继承时这是一种不同但相似的情况,但在没有虚基的多重继承中,编译器可以通过简单地向“this”指针添加位移或修正来解决所有问题基类“嵌入”在派生类的实例中。但是对于虚拟基础,有时需要重写函数。这完全取决于被调用的(甚至是非虚拟的)成员函数访问了哪些数据成员。
例如,如果类 C 定义了一个非虚拟成员函数 C::some_member_func,编译器可能需要这样写:
- C::some_member_func 从 C 的实际实例(而不是 D)调用时,在编译时确定(因为 some_member_func 不是虚函数)
- C::some_member_func 从 D 类的实际实例调用相同的成员函数,在编译时确定。 (从技术上讲,这个例程是 D::some_member_func。尽管这个成员函数的定义是隐式的并且与 C::some_member_func 的源代码相同,但生成的目标代码可能会略有不同。)
如果 C::some_member_func 的代码碰巧使用了在 A 类和 C 类中定义的成员变量。