上一篇https://blog.csdn.net/a15929748502/article/details/80941931中我们已经一起探索了含有虚函数较为普通是的多继承的对象模型,这一讲我们来一起看看菱形继承时的对象模型。(如果了解菱形继承可以参考这篇博文https://blog.csdn.net/a15929748502/article/details/80898126)
下面就由我来构造一个含有虚函数的菱形继承的场景
#include <iostream>
using namespace std;
class B
{
public :
int _b ;
virtual void FuncA()
{
cout << " ---------->B::FuncA ..." << endl;
}
virtual void FuncB()
{
cout << " ---------->B::FuncB ..." << endl;
}
};
class B1:public B
{
public :
int _b1 ;
virtual void FuncA()
{
cout << " ---------->B1::FuncA ..." << endl;
}
virtual void FuncC()
{
cout << " ---------->B1::FuncC ..." << endl;
}
};
class B2:public B
{
public :
int _b2 ;
virtual void FuncB()
{
cout << " ---------->B2::FuncB ..." << endl;
}
virtual void FuncD()
{
cout << " ---------->B2::FuncD ..." << endl;
}
};
class D : public B1,public B2
{
public :
int _d;
virtual void FuncE()
{
cout << " ---------->D::FuncE ..." << endl;
}
};
typedef void (*Func)();
int main ()
{
B1 b1;
B2 b2;
D d;
b1._b =1;
b1._b1=2;
b2. _b=3;
b2._b2=4;
d.B1::_b =5;
d.B2::_b =6;
d._b1=7;
d._b2=8;
d._d=9;
long **p1=(long**)&b1;
long **p2=(long**)&b2;
long **pd=(long**)&d;
cout << "B1" << endl;
cout <<*((int*)&b1) << endl;
((Func)p1[0][0])();
((Func)p1[0][1])();
((Func)p1[0][2])();
cout <<*((int*)&b1+1) << endl;
cout <<*((int*)&b1+2) << endl;
cout << "sizeof(B1) = " << sizeof(B1) << endl;
cout <<"---------------------------------------------------------" << endl;
cout << "B2" << endl;
cout <<*((int*)&b2) << endl;
((Func)p2[0][0])();
((Func)p2[0][1])();
((Func)p2[0][2])();
cout <<*((int*)&b2+1) << endl;
cout <<*((int*)&b2+2) << endl;
cout << "sizeof(B2) = " << sizeof(B2) << endl;
cout <<"---------------------------------------------------------" << endl;
cout << "D" << endl;
cout <<*((int*)&d) << endl;
((Func)pd[0][0])();
((Func)pd[0][1])();
((Func)pd[0][2])();
((Func)pd[0][3])();
cout <<*((int*)&d+1) << endl;
cout <<*((int*)&d+2) << endl;
cout <<*((int*)&d+3) << endl;
((Func)pd[3][0])();
((Func)pd[3][1])();
((Func)pd[3][2])();
cout <<*((int*)&d+4) << endl;
cout <<*((int*)&d+5) << endl;
cout <<*((int*)&d+6) << endl;
cout << "sizeof(D) = " << sizeof(D) << endl;
}
运行一下,打印结果为
可以看到派生类D的大小为28个字节,
下面是我画出的对象模型
也就是
这是大家发现了一个老问题,就是在菱形继承里,B的成员变量在D中存了两份,这显然是不合理的,不仅浪费了空间,还产生了二义性,这次我们还是用虚拟继承的方式解决它,那么我们就有必要继续向下探索
虚拟继承
class B
{
public:
virtual void Fun1()
{
cout << "B::Fun1()" << endl;
}
virtual void Fun2()
{
cout << "B::Fun2()" << endl;
}
public:
int _b;
};
class D :virtual public B
{
public:
int _d;
};
int main()
{
D d;
cout << sizeof(d) << endl;
d._b = 1;
d._d = 2;
PrintFun(d);
}
在基类B中我们定义了两个虚函数Fun1和Fun2以及一个成员变量_b,派生类D虚拟继承基类B,D中只定义了一个成员变量_d,我们知道虚拟继承的对象模型是倒立的,基类在下,派生类在上,前四个字节放的是偏移量表的地址,该表中前四个字节是相对派生类对象起始地址的偏移量,后四个字节是基类的相对该位置的偏移量,通过该表可以找到基类在该对象中对应的位置。由于派生类D未对基类中的虚函数进行重写,因此基类虚表无改动,对应下图
若在派生类中对Fun1重写,并且加上虚函数Fun3,其对象模型会是什么样嘞
class D :virtual public B
{
public:
virtual void Fun1()
{
cout << "C1::Fun1()" << endl;
}
virtual void Fun3()
{
cout << "C1::Fun3()" << endl;
}
public:
int _d;
};
重写Fun1后,基类B对应的虚表被改写,该对象模型对应的前四个字节放的也是一个虚表的地址,在这个虚表中只有派生类中新加入的虚函数;下面四个字节是偏移量表的地址。
菱形虚拟继承
菱形虚拟继承解决了菱形继承中的二义性问题,我们接下来看一看加入虚函数后它的对象模型是怎么样的。
代码与上面菱形继承的代码相同,只是在C1和C2类继承基类B时加上了virtual关键字。
int main()
{
D d;
cout << sizeof(d) << endl;
d._c1 = 1;
d._c2 = 2;
d._d = 3;
d._b = 4;
C1& c1 = d;
PrintFun(c1);
C2& c2 = d;
PrintFun(c2);
B& b = d;
PrintFun(b);
}
其对象模型见下图,基类B在整个对象模型中只有一份,并且存放在最下面,由于先继承自C1类,因此C1类对应部分放在上面,其中前四个字节为C1的虚表地址,派生类D对Fun3重写,因此调用D中的Fun3,D中新添加的Fun5也被放入C1的虚表中,方便查找,接着四个字节是偏移量表的地址,该表前四个字节是相对于C1所对应部分起始位置的偏移量,后四个字节是基类B对应部分相对该位置的偏移量,再下面四个字节存放的是C1类的成员变量_c1。C2和C1类似,基类B中前四个字节是B对应的虚表地址,该虚表中Fun1先在C1中被重写,再在D中被重写,Fun2在C2中被重写,最后被改写成了下面这样的造型
带虚函数的各种继承其对象模型就分析到这里,over!
从虚拟菱形继承转载自https://blog.csdn.net/shidantong/article/details/80424618