定义:两个子类继承同一个父类,而又有子类同时继承这两个子类。
如果直接继承会引发访问不明确(二义性),以及数据冗余。如果直接指定访问对象,可解决二义性(第一段代码以及解析图),而要解决数据冗余,则要引入虚函数(第二段代码以及解析图)。
代码一:
#include <iostream>
using namespace std;
class A
{
public:
int _a;
};
class B : public A
{
public:
int _b;
};
class C : public A
{
public:
int _c;
};
class D : public C, public B
{
public:
int _d;
};
int main()
{
D dd;
dd.C::_a =2;
dd._c = 4;
dd.B::_a =1;
dd._b = 3;
dd._d = 5;
return 0;
}
由上图可以看出,继承顺序为C B , D的继承方式决定创建空间的先后。指定访问对象可以解决二义性,却无法解决数据冗余。
代码二:
#include <iostream>
using namespace std;
class A
{
public:
int _a;
};
class B : virtual public A
{
public:
int _b;
int _b1;
};
class C : virtual public A
{
public:
int _c;
};
class D : public B, public C
{
public:
int _d;
};
int main()
{
D dd;
dd.B::_a =1;
dd._b = 3;
dd._b1 = 6;
dd.C::_a =2;
dd._c = 4;
dd._d = 5;
return 0;
}
对比上图,由于vptr的存在,空间加大,解决了数据冗余的问题。下面为实现过程
现在我们查看dd对象创建的过程。
1.进入反汇编
D dd;
010B4BC8 push 1
010B4BCA lea ecx,[dd]
010B4BCD call D::D (010B1442h) //dd创建
dd.B::_a = 1;
010B4BD2 mov eax,dword ptr [dd]
010B4BD5 mov ecx,dword ptr [eax+4]
010B4BD8 mov dword ptr dd[ecx],1 //写入1
dd._b = 3;
010B4BE0 mov dword ptr [ebp-20h],3
dd._b1 = 6;
010B4BE7 mov dword ptr [ebp-1Ch],6
dd.C::_a = 2;
010B4BEE mov eax,dword ptr [dd]
010B4BF1 mov ecx,dword ptr [eax+4]
010B4BF4 mov dword ptr dd[ecx],2
dd._c = 4;
010B4BFC mov dword ptr [ebp-14h],4
dd._d = 5;
010B4C03 mov dword ptr [ebp-10h],5
return 0;
2.进入dd创建语句
D::D:(部分语句)
//之前语句对dd进行了初始化。
010B2D47 je D::D+3Ch (010B2D5Ch)
010B2D49 mov eax,dword ptr [this] //将dd首地址送往eax.
010B2D4C mov dword ptr [eax],10BCCCCh //将B距离A的偏移地址
//送到eax.
010B2D52 mov eax,dword ptr [this]
010B2D55 mov dword ptr [eax+0Ch],10BCCE8h //将C距离A的地址
//送到eax.
010B2D5C push 0
010B2D5E mov ecx,dword ptr [this]
010B2D61 call B::B (010B143Dh) //B的构造
010B2D66 push 0
010B2D68 mov ecx,dword ptr [this]
010B2D6B add ecx,0Ch //将ecx+B的大小
010B2D6E call C::C (010B1447h) //C的构造
010B2D73 mov eax,dword ptr [this]
进入B的构造(部分)
010B3049 mov eax,dword ptr [this]
010B304C mov dword ptr [eax],10BCC70h //存放B的大小
010B3052 mov eax,dword ptr [this]
进入C的构造部分
010B3119 mov eax,dword ptr [this]
010B311C mov dword ptr [eax],10BCCB8h //存放C的大小
010B3122 mov eax,dword ptr [this]
dd对象在创建的过程中,调用D的构造,然后对B和C进构造,在B构造时,在其起始位置(dd位置)放置一指向B的父类(A)的指针的偏移地址,偏移地址+4便是距离A的偏移量,同时在创建一偏移地址,保存B的对象的大小。然后dd加上B的对象的空间大小,在之后创建B的对象。B和A的对象的首地址指向同一空间(父类A)。
ps:对于偏移地址当前内容是为其他偏移量预留,在菱形继承和多态进行共同探究时发现那个位置存放的是距离this的偏移量。