C++ 多重继承中虚拟表的理解
我有一个类实现了两个抽象类,如下所示。没有虚拟继承。没有数据成员C++ 多重继承中虚拟表的理解,c++,C++,我有一个类实现了两个抽象类,如下所示。没有虚拟继承。没有数据成员 class IFace1 { public: virtual void fcn(int abc) = 0; }; class IFace2 { public: virtual void fcn1(int abc) = 0; }; class RealClass: public IFace1, public IFace2 { public: void fcn(int a) { } voi
class IFace1 {
public:
virtual void fcn(int abc) = 0;
};
class IFace2 {
public:
virtual void fcn1(int abc) = 0;
};
class RealClass: public IFace1, public IFace2 {
public:
void fcn(int a) {
}
void fcn1(int a) {
}
};
我发现RealClass的vtable和object内存布局如下所示
Vtable for RealClass
RealClass::_ZTV9RealClass: 7u entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI9RealClass)
16 (int (*)(...))RealClass::fcn
24 (int (*)(...))RealClass::fcn1
32 (int (*)(...))-8
40 (int (*)(...))(& _ZTI9RealClass)
48 (int (*)(...))RealClass::_ZThn8_N9RealClass4fcn1Ei
Class RealClass
size=16 align=8
base size=16 base align=8
RealClass (0x2af836d010e0) 0
vptr=((& RealClass::_ZTV9RealClass) + 16u)
IFace1 (0x2af836cfa5a0) 0 nearly-empty
primary-for RealClass (0x2af836d010e0)
IFace2 (0x2af836cfa600) 8 nearly-empty
vptr=((& RealClass::_ZTV9RealClass) + 48u)
我对此感到困惑。什么是RealClass::\u ZThn8\u N9RealClass4fcn1Ei?为什么IFace2的vptr指出了这一点?当我从IFace2*调用fcn1时会发生什么?程序如何在RealClass的Vtable中找到RealClass::fcn1?我想它一定需要使用IFace2 vptr,但不清楚具体如何使用。警告:下面的大部分内容当然是实现和平台相关的,并且是简化的。我将遵循在您的示例中实现它的方式——可能是GCC,64位
首先,虚拟类实例的契约是什么?例如,如果您有一个变量
IFace1*obj
:
- 在obj+0有一个指向虚拟表的指针
- 任何成员数据字段都将在obj+8处继续(
)sizeof(void*)
- 虚拟表包含一条指向
atvtbl+0的记录void fcn(int)
- 在该表中,还有一个指针指向
vtbl-8处的类的
(由typeinfo
等使用)和vtbl-16处的“基偏移”dynamic\u cast
IFace1*
的变量的函数都可以依赖于该值为真。对于IFace2*
,情况类似
- 如果他们想调用虚拟函数
,他们查看obj+0以获取vtable,然后在vtbl+0调用在那里找到的地址<代码>此设置为objvoid fcn(int)
- 如果他们想访问成员字段(自己访问,例如,如果该字段具有公共访问权限,或者如果有内联访问器),他们只需在其地址obj+xxx读取/写入成员即可
- 如果他们想知道自己真正拥有的类型,他们会从对象的地址中减去vtbl-16的值,然后查看基本对象引用的vtable的
指针typeinfo
现在,对于具有多重继承的类,编译器如何满足这些要求 1)首先,它需要为自己生成结构。虚拟表指针必须位于obj+0处,所以它就在那里。这张桌子看起来怎么样?很明显,基的偏移量是0,很容易生成
typeinfo
数据和指向它的指针,然后是第一个虚函数和第二个虚函数,没有什么特别的。任何知道RealClass
定义的人都可以进行相同的计算,因此他们知道在vtable等中的何处可以找到函数
2)然后它可以让RealClass
作为IFace1
传递。因此,它需要在对象中的某个位置有一个指向IFace1
格式的虚拟表的指针,那么虚拟表必须有一条void fcn(int)
的记录
编译器很聪明,可以重用它生成的第一个虚拟表,因为它符合这些要求。如果有任何成员字段,它们将存储在指向虚拟表的第一个指针之后,因此即使是它们也可以像派生类是基类一样简单地访问。到目前为止还不错
3)最后,如何处理该对象,以便其他人能够将其用作IFace2
?已创建的vtable不能再使用,因为IFace2
需要其void fcn1(int)
位于vtbl+0
因此,将创建另一个虚拟表,即在转储中第一个虚拟表之后立即看到的虚拟表,并且指向它的指针存储在下一个可用位置的RealClass
中。第二个表需要将“偏移到基准”设置为-8,因为实际对象从偏移量-8开始。它只包含指向IFace2
虚拟函数的指针,void fcn1(int)
然后,对象中的虚拟指针(偏移量obj+8)后面是IFace2
的任何成员数据字段,这样,当指针指向此接口时,任何继承或内联函数都可以再次工作
好的,现在如何从
IFace2
调用fcn1()
?对RealClass::fcn1(int)的非虚拟thunk是什么
如果您将RealClass*
指针传递给一个使用IFace2*
的陌生函数,编译器将发出代码将指针增加8(或无论多么大的sizeof(void*)+sizeof(IFace1)
),以便该函数获取以IFace2的虚拟表指针开始的指针,然后是它的成员字段——正如我前面概述的合同中所约定的
当该函数想要调用void IFace2::fcn1(int)
时,它会查看虚拟表,找到该特定函数的记录(第一个也是唯一一个)并调用它,将this
设置为作为指向IFace2
的指针传递的地址
这里出现了一个问题:如果有人在RealClass
指针上调用RealClass
中实现的这个方法,this
指向RealClass
的底部。与IFace1
相同。但是,如果有人使用指向IFace2
接口的指针调用它,此
会将8个字节(或多个字节)指向对象
因此,编译器需要多次生成函数以适应这种情况,否则它无法正确访问成员字段和其他方法,因为它根据调用方法的人而有所不同
编译器optimi没有让代码真正重复两次