一个开始的例子。
class A
{
public:
virtual const char* GetName() { return "A"; }
};
class B: public A
{
public:
virtual const char* GetName() { return "B"; }
};
class C: public B
{
public:
virtual const char* GetName() { return "C"; }
};
class D: public C
{
public:
virtual const char* GetName() { return "D"; }
};
int main()
{
C cClass;
A &rBase = cClass;
cout << "rBase is a " << rBase.GetName() << endl;
return 0;
}
在此特定示例中,输出为:
rBase是C
以下是它的工作原理:
rBase是A类型的指针,因此它转到A类并查找GetName()。但GetName()是虚拟的,所以编译器会检查A和C之间的所有类,并从最派生的类中获取GetNAme()函数,即C
但是,我怀疑编译器将如何知道哪个是A类的子代,以及如何从Parent类转移到子类等等?一个孩子知道它的父母,但父母不知道它的孩子(我想!)。
从我的观点来看,正确的执行步骤应该是:
rBase是A类型的指针,因此它转到A类并查找GetName()。但GetName()在那里是虚拟的,因此编译器会检查指针指向的类。在这种情况下是C类对象,因此转到C类并检查它是否具有GetName()函数,因此使用它。如果C类中没有该函数(假设),编译器可以轻松跟踪C的父级并检查相同的内容,并且这可以继续,直到它返回到A(假设除A之外的所有类都不包含GetName())。
现在,这似乎是一种更合乎逻辑的方法,因为在继承树中向后移动(子级到父级)似乎比向前移动(父级到子级)更容易实现。
此致
答案 0 :(得分:4)
您描述的第二个算法是“正确的”,因为它可以通过这种方式有效地解决问题。但是,编译器使用一个很好的技巧来快进该算法。基本上,它使用通常称为virtual method table的查找,通常缩写为“vtable”。
基本上,具有虚方法的类的实例包含指向其类的虚方法表的指针。编译器将虚拟方法名称映射到虚拟表中的偏移量,这样调用虚拟表就不需要复杂的算法:所有必要的是数组查找,然后在结果地址处调用。
答案 1 :(得分:2)
rBase是A类型的指针,因此它转到A类并查找GetName()。但GetName()是虚拟的,所以编译器会检查A和C之间的所有类,并从最派生的类中获取GetNAme()函数,即C
不,这不是它的工作原理。生成通过指向virtual GetName()
的指针调用A
的代码时,编译器只是将“调用与对象关联的虚拟表中的第7个条目”。生成覆盖GetName()
的派生类时,编译器会将其GetName()
实现放在虚拟表中的此条目中。
这样父类不需要知道它的子节点。孩子们有责任正确填写虚拟桌子。