虚继承
1. 菱形继承
1)概念
B,C继承自A,D继承自B,C
2)问题
一个派生类中保留间接基类的多份同名成员,可能出现命名冲突和冗余数据问题
2. 虚继承
c++代码示例:
#include <stdio.h>
//定义家具类,虚基类,等同于类A
class Furniture {
public:
Furniture() {
printf("Furniture::Furniture()
");
price = 0;
}
virtual ~Furniture(){ //家具类的虚析构函数
printf("Furniture::~Furniture()
");
}
virtual int getPrice() { //获取家具价格
printf("Furniture::getPrice()
");
return price;
};
protected:
int price; //家具类的成员变量
};
//定义沙发类,继承自类Furniture,等同于类B
class Sofa : virtual public Furniture {
public:
Sofa() {
printf("Sofa::Sofa()
");
price = 1;
color = 2;
}
virtual ~Sofa() { //沙发类虚析构函数
printf("Sofa::~Sofa()
");
}
virtual int getColor() { //获取沙发颜色
printf("Sofa::getColor()
");
return color;
}
virtual int sitDown() { //沙发可以坐下休息
return printf("Sofa::sitDown()
");
}
protected:
int color; // 沙发类成员变量
};
//定义床类,继承自类Furniture,等同于类C
class Bed : virtual public Furniture {
public:
Bed() {
printf("Bed::Bed()
");
price = 3;
length = 4;
width = 5;
}
virtual ~Bed(){ //床类的虚析构函数
printf("Bed::~Bed()
");
}
virtual int getArea(){ //获取床面积
printf("Bed::getArea()
");
return length * width;
}
virtual int sleep(){ //床可以用来睡觉
return printf("Bed::sleep()
");
}
protected:
int length; //床类成员变量
int width;
};
//子类沙发床的定义,派生自类Sofa和类Bed,等同于类D
class SofaBed : public Sofa, public Bed {
public:
SofaBed() {
printf("SofaBed::SofaBed()
");
height = 6;
}
virtual ~SofaBed(){ //沙发床类的虚析构函数
printf("SofaBed::~SofaBed()
");
}
virtual int sitDown(){ //沙发可以坐下休息
return printf("SofaBed::sitDown()
");
}
virtual int sleep(){ //床可以用来睡觉
return printf("SofaBed::sleep()
");
}
virtual int getHeight() {
printf("SofaBed::getHeight()
");
return height;
}
protected:
int height; //沙发类的成员变量
};
int main(int argc, char* argv[]) {
SofaBed sofabed;
Furniture *p1 = &sofabed; //转换成虚基类指针
Sofa *p2 = &sofabed; //转换成父类指针
Bed *p3 = &sofabed; //转换成父类指针
printf("%p %p %p
", p1, p2, p3);
return 0;
}
vs_x86汇编标识:
00401000 push ebp
00401001 mov ebp, esp
00401003 sub esp, 40h
00401006 push 1 ;是否构造虚基类的标志:1构造,0不构造 ①
00401008 lea ecx, [ebp-40h] ;传入对象的首地址作为this指针
0040100B call sub_4011D0 ;调用构造函数 ②
00401010 lea eax, [ebp-40h] ;获取对象的首地址
00401013 test eax, eax
00401015 jnz short loc_401020 ;检查代码
00401017 mov dword ptr [ebp-4], 0
0040101E jmp short loc_40102D
00401020 mov ecx, [ebp-3Ch] ;取出对象中的Sofa类虚基类偏移表第二项数据
;即虚基类对象首地址相对于虚基类偏移表的偏移值
00401023 mov edx, [ecx+4] ;取出偏移值并存入edx
00401026 lea eax, [ebp+edx-3Ch] ;根据虚基类偏移表,得到虚基类数据的所在地址 ③
0040102A mov [ebp-4], eax ;利用中间变量保存虚基类的首地址
0040102D mov ecx, [ebp-4]
00401030 mov [ebp-14h], ecx ;p1=&sofabed
00401033 lea edx, [ebp-40h] ;直接转换SofaBed对象的首地址为父类Sofa的指针 ④
00401036 mov [ebp-10h], edx ;p2=&sofabed
00401039 lea eax, [ebp-40h] ;获取对象SofaBed的首地址 ⑤
0040103C test eax, eax
0040103E jz short loc_40104B ;地址检查
00401040 lea ecx, [ebp-40h]
00401043 add ecx, 0Ch ;获取Bed类对象的首地址
00401046 mov [ebp-8], ecx ;利用中间变量保存Bed类对象的首地址
00401049 jmp short loc_401052
0040104B mov dword ptr [ebp-8], 0
00401052 mov edx, [ebp-8]
00401055 mov [ebp-0Ch], edx ;p3=&sofabed
00401058 mov eax, [ebp-0Ch]
0040105B push eax ;参数4:p3
0040105C mov ecx, [ebp-10h]
0040105F push ecx ;参数3:p2
00401060 mov edx, [ebp-14h]
00401063 push edx ;参数2:p1
00401064 push offset aPPP ;参数1:"%p %p %p
"
00401069 call sub_401650 ;调用printf函数
0040106E add esp, 10h
00401071 mov dword ptr [ebp-18h], 0
00401078 lea ecx, [ebp-40h] ;传递this指针
0040107B call sub_4013E0 ;调用析构代理函数 ⑥
00401080 mov eax, [ebp-18h]
00401083 mov esp, ebp
00401085 pop ebp
00401086 retn
;虚继承结构的子类构造
004011D0 push ebp
004011D1 mov ebp, esp
004011D3 sub esp, 8
004011D6 mov [ebp-4], ecx
004011D9 mov dword ptr [ebp-8], 0
004011E0 cmp dword ptr [ebp+8], 0 ;比较参数是否为0,为0则执行跳转,防止重复构造虚基类
004011E4 jz short loc_401209
004011E6 mov eax, [ebp-4]
004011E9 mov dword ptr [eax+4], offset unk_4122B0 ;设置父类Sofa中的虚基类偏移表 ②
004011F0 mov ecx, [ebp-4]
004011F3 mov dword ptr [ecx+10h], offset unk_4122B8;设置父类Bed中的虚基类偏移表 ③
004011FA mov ecx, [ebp-4]
004011FD add ecx, 20h ;调整this指针为虚基类this指针
00401200 call sub_401120 ;调用虚基类构造函数 ④
00401205 or dword ptr [ebp-8], 1
00401209 push 0 ;传入0作为构造标记
0040120B mov ecx, [ebp-4] ;获取Sofa对象首地址作为this指针
0040120E call sub_401150 ;调用父类Sofa构造函数 ⑤
00401213 push 0 ;传入0作为构造标记
00401215 mov ecx, [ebp-4] ;调整this指针
00401218 add ecx, 0Ch ;获取Bed对象首地址作为this指针
0040121B call sub_401090 ;调用父类Bed构造函数 ⑥
00401220 mov edx, [ebp-4]
00401223 mov dword ptr [edx], offset ??_7SofaBed@@6B@ ;覆盖Sofa类虚表指针(新) ⑦
00401229 mov eax, [ebp-4]
0040122C mov dword ptr [eax+0Ch], offset ??_7SofaBed@@6B@_0 ;覆盖Bed类虚表指针(新) ⑧
00401233 mov ecx, [ebp-4] ;通过this指针和虚基类偏移表定位到虚基类的虚表指针
00401236 mov edx, [ecx+4] ;虚基类偏移表给edx
00401239 mov eax, [edx+4] ;虚基类虚表指针相对于虚基类偏移表的偏移给eax
0040123C mov ecx, [ebp-4] ;获取this指针
0040123F mov dword ptr [ecx+eax+4], offset ??_7SofaBed@@6B@_1;覆盖Furniture类虚表指针(新) ⑨
00401247 push offset aSofabedSofabed;"SofaBed::SofaBed()
"
0040124C call sub_401650 ;调用printf函数
00401251 add esp, 4
00401254 mov edx, [ebp-4]
00401257 mov dword ptr [edx+1Ch], 6;height = 6
0040125E mov eax, [ebp-4]
00401261 mov esp, ebp
00401263 pop ebp
00401264 retn 4
;子类析构代理函数
004013E0 push ebp ;析构代理函数
004013E1 mov ebp, esp
004013E3 push ecx
004013E4 mov [ebp-4], ecx
004013E7 mov ecx, [ebp-4]
004013EA add ecx, 20h
004013ED call sub_401320 ;调用SofaBed的析构函数
004013F2 mov ecx, [ebp-4]
004013F5 add ecx, 20h ;调整this指针为虚基类
004013F8 call sub_4012B0 ;调用虚基类的析构函数
004013FD mov esp, ebp
004013FF pop ebp
00401400 retn
;子类的析构函数
00401320 push ebp
00401321 mov ebp, esp
00401323 push ecx
00401324 mov [ebp-4], ecx
00401327 mov eax, [ebp-4] ;调整this指针为Sofa,还原虚表指针为SofaBed
0040132A mov dword ptr [eax-20h], offset ??_7SofaBed@@6B@
00401331 mov ecx, [ebp-4] ;调整this指针为Bed,还原虚表指针为SofaBed
00401334 mov dword ptr [ecx-14h], offset ??_7SofaBed@@6B@_0
0040133B mov edx, [ebp-4]
0040133E mov eax, [edx-1Ch]
00401341 mov ecx, [eax+4] ;从虚基类偏移表中获取虚基类偏移
00401344 mov edx, [ebp-4]
;调整this指针为虚基类,还原虚表指针为SofaBed,到此为止,3个虚表指针还原完毕,执行析构函数内的代码
00401347 mov dword ptr [edx+ecx-1Ch], offset ??_7SofaBed@@6B@_1
0040134F push offset aSofabedSofabed_0;"SofaBed::~SofaBed()
"
00401354 call sub_401650 ;调用printf函数
00401359 add esp, 4
0040135C mov ecx, [ebp-4]
0040135F sub ecx, 4 ;调整this指针为Bed
00401362 call sub_401270 ;调用父类Bed析构函数
00401367 mov ecx, [ebp-4]
0040136A sub ecx, 14h ;调整this指针为Sofa
0040136D call sub_4012E0 ;调用父类Sofa析构函数
00401372 mov esp, ebp
00401374 pop ebp
00401375 retn
;Sofe类虚基类偏移表
.rdata:004122B0 dword_4122B0 dd 0FFFFFFFCh ;-4,虚基类偏移表所属类对应的对象首地址相对于虚基类偏移表的偏移值
.rdata:004122B4 dd 1Ch ;虚基类对象首地址相对于虚基类偏移表的偏移值
;Bed类虚基类偏移表
.rdata:004122B8 dword_4122B8 dd 0FFFFFFFCh
.rdata:004122BC dd 10h
1)方式
class Bed : virtual public Furniture{};
2)作用
可以避免共同派生出的子类产生多义性错误
3)子类对象内存结构
①B父类的虚表指针,B父类虚基类偏移表,B父类独有的成员
②C父类的虚表指针,C父类虚基类偏移表,C父类独有的成员
③子类自己的成员
④A祖父类的虚表指针,A祖父类自己独有的成员
注意:当子类存在多个虚基类时,会在虚基类偏移表中依次记录它们的偏移量
4)流程
①传入构造标记1,表示需要构造虚基类
②调用子类构造代理函数
a.设置父类Sofa中的虚基类偏移表
b.设置父类Bed中的虚基类偏移表
c.调用虚基类构造函数
d.传入构造标记0,表示不需要构造虚基类,调用父类Sofa构造函数
e.传入构造标记0,表示不需要构造虚基类,调用父类Bed构造函数
f.覆盖Sofa类虚表指针(新)
g.覆盖Bed类虚表指针(新)
h.覆盖Furniture类虚表指针(新)
i.调用子类构造函数
③根据虚基类偏移表,得到虚基类数据的所在地址,转换子类对象的对应地址处为虚基类对象
④直接转换SofaBed对象的首地址为父类Sofa的指针
⑤通过对象首地址+偏移找到Bed类的地址,转换子类对象的对应地址处为Bed类对象
⑥调用析构代理函数
a..调整this指针为Sofa,还原虚表指针为SofaBed
b.调整this指针为Bed,还原虚表指针为SofaBed
c.从虚基类偏移表中获取虚基类偏移,调整this指针为虚基类,还原虚表指针为SofaBed
d.调用子类析构函数
e.调用父类Bed析构函数
f.调用父类Sofa析构函数
g.调用虚基类析构函数
5)子类构造顺序
①Furniture
②Sofa(根据标记跳过Furniture构造)
③Bed(根据标记跳过Furniture构造)
④SofaBed自身
注意:
①vs编译器使用构造标记决定是否跳过所有父类的构造,只构造自身
②GCC编译器生成一个父类构造代理函数,该函数不调用虚基类构造函数;对于正常实例化的父类对象,GCC编译器不会调用该构造代理函数