了解多重继承中的虚拟表
Understanding virtual table in multiple inheritance
我有一个实现两个抽象类的类,如下所示。没有虚拟继承。没有数据成员。
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和对象内存布局如下。
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::_ZThn8_N9RealClass4fcn1Ei?为什么IFace2的vptr指出了这一点?当我从IFace2*调用fcn1时会发生什么?程序如何在RealClass的Vtable中找到RealClass::fcn1?我想它在某种程度上需要使用IFace2vptr,但不清楚具体如何使用。
警告:下面的大部分内容当然是实现的,依赖于平台并简化了。我将按照我在您的示例中看到的方式来实现它——可能是GCC,64位
首先,虚拟类实例的契约是什么?例如,如果您有一个变量IFace1* obj
:
- 在obj+0处有一个指向虚拟表的指针
- 任何成员数据字段都将在obj+8(
sizeof(void*)
)处继续 - 虚拟表包含一条记录,该记录指向vtbl+0处的
void fcn(int)
- 在表中,还有一个指向vtbl-8处的类的
typeinfo
的指针(由dynamic_cast
等使用)和vtbl-16的">到基的偏移量">
任何看到类型为IFace1*
的变量的函数都可以依赖于此是否为真。与IFace2*
类似。
- 如果他们想调用虚拟函数
void fcn(int)
,他们会查看obj+0以获取vtable,然后查看vtbl+0bj - 如果他们想访问成员字段(自己访问,例如,如果该字段具有公共访问权限,或者存在内联访问器),他们只需在其地址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()
?那是什么non-virtual thunk to RealClass::fcn1(int)
?
如果您将RealClass*
指针传递给一个使用IFace2*
的陌生函数,编译器将发出代码,将指针增加8(或者无论sizeof(void*) + sizeof(IFace1)
有多大),以便该函数获得以IFace2
的虚拟表指针开始的指针,然后是其成员字段——正如我之前概述的契约中所约定的那样。
当该函数想要调用void IFace2::fcn1(int)
时,它会查看虚拟表,转到该特定函数(第一个也是唯一一个)的记录并调用它,其中this
设置为作为指向IFace2
的指针传递的地址。
这里出现了一个问题:如果有人在RealClass
指针上调用RealClass
中实现的这个方法,则this
指向RealClass
的基。与CCD_ 38相同。但是,如果它是由具有指向IFace2
接口的指针的人调用的,那么this
会将8个(或多个)字节指向对象!
因此,编译器需要多次生成函数来适应这种情况,否则它无法正确访问成员字段和其他方法,因为它会因调用该方法的人而异。
编译器通过创建隐藏的隐式小型thunk函数来优化这一点,而不是让代码真正重复两次,它只是
- 将CCD_ 41指针减少适当的量
- 调用真正的方法,无论是谁调用它,它现在都可以正常工作
- 虚拟继承中是否存在多重继承?
- 多重继承导致虚假的模糊虚拟函数过载
- 仅函数的多重继承 - 没有虚拟和 CRTP
- 使用具有虚拟多重继承的基构造函数
- 具有虚拟多重继承的构造函数定义
- 通过相同的纯虚拟方法,来自不同抽象基础的C 多重继承
- 虚拟继承如何解决 c++ 中的多重继承(钻石)?它将走哪条路
- C 多重继承,虚拟方法覆盖问题和协变量返回类型
- 关于C 接口(纯虚拟类)/多重继承/虚拟继承的设计Qustion
- 纯虚拟继承、多重继承和C4505
- 虚拟多重继承和强制转换
- 使用虚拟基类时,多重继承是如何工作的
- 纯虚拟函数和多重继承
- 可以通过虚拟支持多重继承
- 如何使用多重继承内联虚拟
- 虚拟类多重继承
- MinGW 4.7.0 到 4.7.2 错误:使用混合虚拟和非虚拟多重继承时成员函数中的"this"指针无效
- C++纯虚拟多重继承
- 如果QObject是从direct派生的,那么使用“虚拟”多重继承是否安全?
- 虚拟多重继承和指针