C++虚函数的作用及实现原理(三)(含有虚函数的类的对象模型之菱形继承)

上一篇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








猜你喜欢

转载自blog.csdn.net/a15929748502/article/details/80949673
今日推荐