请考虑以下代码:
#include <iostream>
class A
{
public:
virtual void f() = 0;
virtual void g() = 0;
};
class B : virtual public A
{
public:
virtual void f()
{
g();
}
};
class C : virtual public A
{
public:
virtual void g()
{
std::cout << "C::g" << std::endl;
}
};
class D : public C, public B
{
};
int main()
{
B* b = new D;
b->f();
}
以下程序的输出为C::g
。
编译器如何调用类B ??
的姐妹类的函数答案 0 :(得分:6)
N3337 10.3 / 9
[注意:对虚函数的调用的解释取决于它所对象的类型 调用(动态类型),而非虚拟成员函数调用的解释取决于 仅表示该对象的指针或引用的类型(静态类型)(5.2.2)。 - 结束说明]
动态类型是指针真正指向的类型,而不是声明指定类型的类型。
因此:
D d;
d.g(); //this results in C::g as expected
与:
相同B* b = new D;
b->g();
因为B::f
对g()
的调用({em>隐式)调用了this
指针,其动态类型为{ {1}},来电解析为D
,即D::f
。
如果你仔细观察,那就是上面代码中显示的(完全)相同的行为,只有C::f
现在隐含 b
代替。
这就是虚函数的重点。
答案 1 :(得分:2)
这是virtual
的行为:B
通过g
调用f
,但g
在运行时解析(如f
)。因此,在运行时,g
的唯一可用覆盖D
是C
答案 2 :(得分:1)
g
在运行时解析。由于D
的定义方式,它被解析为C实现的任何东西。
如果您不想要这种行为,您应该调用g
的非虚拟实现(您也可以从虚拟实体委托该功能),或者显式调用B
'使用B::g()
实现。
虽然如果你这样做,你的设计会比它可能需要的要复杂得多,所以试着找到一个不依赖所有这些技巧的解决方案。
答案 3 :(得分:0)
虚拟函数调用在运行时通过引用实例的VTable
来解析。任何虚拟类都存在明显的VTable
(因此,A
,B
,C
和D
各有一个VTable
。每个运行时实例都有一个指向其中一个表的指针,由其动态类型决定。
VTable
列出了类中的每个虚函数,将其映射到应该在运行时调用的实际函数。对于正常继承,这些是按声明的顺序列出的,因此基类可以使用派生类的VTable
来解析它声明的虚函数(因为派生类,按声明顺序列出函数,将具有所有基类的函数首先列在基类自己的VTable
的相同顺序中。对于虚拟继承(如上所述),这稍微复杂一些,但实际上派生类中的基类仍然有自己的VTable
指针,指向派生的VTable
内的相关部分。
实际上,这意味着您的D
课程VTable
g
条目指向C
的实施。即使通过B
静态类型进行访问,它仍会引用相同的VTable
来解析g
。