参考资料:https://blog.csdn.net/haoel/article/details/3081385
为什么引入虚继承?
#include <iostream>
class B
{
public:
B() : _ib(10), _cb('B') {}
virtual void f() { std::cout << "B::f()" << std::endl; }
virtual void Bf() { std::cout << "B::Bf()" << std::endl; }
int _ib;
char _cb;
};
class B1 : public B
{
public:
B1() : _ib1(100), _cb1('1') {}
virtual void f() { std::cout << "B1::f()" << std::endl; }
virtual void f1() { std::cout << "B1::f1()" << std::endl; }
virtual void Bf1() { std::cout << "B1::Bf1()" << std::endl; }
int _ib1;
char _cb1;
};
class B2 : public B
{
public:
B2() : _ib2(1000), _cb2('2') {}
virtual void f() { std::cout << "B2::f()" << std::endl; }
virtual void f2() { std::cout << "B2::f2()" << std::endl; }
virtual void Bf2() { std::cout << "B2::Bf2()" << std::endl; }
int _ib2;
char _cb2;
};
class D : public B1, public B2
{
public:
D() : _id(10000), _cd('3') {}
virtual void f() { std::cout << "D::f()" << std::endl; }
virtual void f1() { std::cout << "D::f1()" << std::endl; }
virtual void f2() { std::cout << "D::f2()" << std::endl; }
virtual void Df() { std::cout << "D::Df()" << std::endl; }
int _id;
char _cd;
};
int main(void)
{
D d;
// std::cout << d._ib << std::endl; 二义性错误错误
std::cout << d.B1::_ib << std::endl;
return 0;
}
1>class D size(48):
1> ±–
1> 0 | ±-- (base class B1)
1> 0 | | ±-- (base class B)
1> 0 | | | {vfptr}
1> 4 | | | _ib
1> 8 | | | _cb
1> | | | (size=3)
1> | | ±–
1>12 | | _ib1
1>16 | | _cb1
1> | | (size=3)
1> | ±–
1>20 | ±-- (base class B2)
1>20 | | ±-- (base class B)
1>20 | | | {vfptr}
1>24 | | | _ib
1>28 | | | _cb
1> | | | (size=3)
1> | | ±–
1>32 | | _ib2
1>36 | | _cb2
1> | | (size=3)
1> | ±–
1>40 | _id
1>44 | _cd
1> | (size=3)
1> ±–
1>D::KaTeX parse error: Expected 'EOF', got '&' at position 19: …able@B1@: 1> | &̲D_meta 1> | 0 …vftable@B2@:
1> | -20
1> 0 | &thunk: this-=20; goto D::f
1> 1 | &B::Bf
1> 2 | &D::f2
1> 3 | &B2::Bf2
上面这个例子可以看到多重继承时,在D的实例中B的成员变量_ib和_cb存在两份,为了消除这种场景下的重复数据引入了虚继承。
将上面的例子改为虚继承(B1 B2虚继承至B)然后再看D的内存分布
1>class D size(56):
1> ±–
1> 0 | ±-- (base class B1)
1> 0 | | {vfptr}
1> 4 | | {vbptr}
1> 8 | | _ib1
1>12 | | _cb1
1> | | (size=3)
1> | ±–
1>16 | ±-- (base class B2)
1>16 | | {vfptr}
1>20 | | {vbptr}
1>24 | | _ib2
1>28 | | _cb2
1> | | (size=3)
1> | ±–
1>32 | _id
1>36 | _cd
1> | (size=3)
1> ±–
1>40 | (vtordisp for vbase B)
1> ±-- (virtual base B)
1>44 | {vfptr}
1>48 | _ib
1>52 | _cb
1> | (size=3)
1> ±–
1>D::KaTeX parse error: Expected 'EOF', got '&' at position 19: …able@B1@: 1> | &̲D_meta 1> | 0 …vftable@B2@:
1> | -16
1> 0 | &D::f2
1> 1 | &B2::Bf2
1>D::
v
b
t
a
b
l
e
@
B
1
@
:
1
>
0
∣
−
41
>
1
∣
40
(
D
d
(
B
1
+
4
)
B
)
1
>
D
:
:
vbtable@B1@: 1> 0 | -4 1> 1 | 40 (Dd(B1+4)B) 1>D::
vbtable@B1@:1>0∣−41>1∣40(Dd(B1+4)B)1>D::vbtable@B2@:
1> 0 | -4
1> 1 | 24 (Dd(B2+4)B)
1>D::$vftable@B@:
1> | -44
1> 0 | &(vtordisp) D::f
1> 1 | &B::Bf
虚继承内存布局特点
看下上例中加了虚继承之后的class B1
1>class B1 size(32):
1> ±–
1> 0 | {vfptr} // B1 新增了两个虚函数f1和Bf1,因此新增了一个自己的虚函数指针
1> 4 | {vbptr} // 虚基指针放在基类B的前面
1> 8 | _ib1
1>12 | _cb1
1> | (size=3)
1> ±–
1>16 | (vtordisp for vbase B)
1> ±-- (virtual base B)
1>20 | {vfptr}
1>24 | _ib
1>28 | _cb
1> | (size=3)
1> ±–
1>B1::KaTeX parse error: Expected 'EOF', got '&' at position 19: …able@B1@: 1> | &̲B1_meta 1> | 0…vbtable@:
1> 0 | -4 // 表示vbptr与B1对象首地址的编译,vbptr前面有个四字节的vfptr
1> 1 | 16 (B1d(B1+4)B) // 基类B相对于虚基指针的偏移 -20-(-4)
1>B1::$vftable@B@:
1> | -20
1> 0 | &(vtordisp) B1::f
1> 1 | &B::Bf
总结:1 虚继承中虚继承的虚基类位于派生类对象的末尾,(虚基指针,虚函数指针,派生类自己成员在基类对象前面,基类对象按声明顺序分布)
2 对于派生类新增的虚函数,派生类对象会产生一个虚函数指针(不同于普通继承会添加到继承的第一个基类的虚函数表中)
3 虚基表中表的第一项表示派生类对象指针相对于虚基指针的偏移,从第二项开始表示各个基类地址相对于虚基指针的偏移