不同继承体系下带虚函数的对象模型

不同继承体系下带虚函数的对象模型

单继承

class B
{
public:
    virtual void Fun1()
    {
        cout << "B::Fun1()" << endl;
    }
    virtual void Fun2()
    {
        cout << "B::Fun2()" << endl;
    }
    virtual void Fun3()
    {
        cout << "B::Fun3()" << endl;
    }
public:
    int _b;
};
class D:public B
{
public:
    virtual void Fun2()
    {
        cout << "D::Fun2()" << endl;
    }
    virtual void Fun3()
    {
        cout << "D::Fun3()" << endl;
    }
public:
    int _d;
};
typedef void(*PVFT)();
void PrintFun(B& b)
{

    PVFT *pVFT = (PVFT*)(*(int*)&b);
    while (*pVFT)
    {
        (*pVFT)();
        pVFT++;
    }
    cout << endl;
}

int main()
{
    D d;

    PrintFun(d);
}

这里写图片描述


多继承

代码代码:

class B1
{
public:
    virtual void Fun1()
    {
        cout << "B1::Fun1()" << endl;
    }
    virtual void Fun2()
    {
        cout << "B1::Fun2()" << endl;
    }
public:
    int _b1;
};
class B2
{
public:
    virtual void Fun1()
    {
        cout << "B2::Fun1()" << endl;
    }
    virtual void Fun2()
    {
        cout << "B2::Fun2()" << endl;
    }
public:
    int _b2;
};
class D:public B1,public B2
{
public:
    virtual void Fun1()
    {
        cout << "D::Fun1()" << endl;
    }
    virtual void Fun3()
    {
        cout << "D::Fun3()" << endl;
    }
public:
    int _d;
};

int main()
{

    D d;
    cout << sizeof(d) << endl;
    d._b1 = 1;
    d._b2 = 2;
    d._d = 3;

    B1& b1 = d;
    PrintFun(b1);

    B2& b2 = d;
    PrintFun(b2);
}

我们先定义了两个基类B1和B2,在这两个基类中分别定义Fun1和Fun2两个虚函数,再定义一个派生类D公有继承B1和B2,在D中对Fun1进行重写,并再新增一个虚函数Fun3,输出D类对象的大小为20

这里写图片描述
通过画图分析我们发现派生类对象中存在两张虚表,分别对应基类B1和B2,派生类中重写虚函数Fun1,对两张虚表中的Fun1都进行了覆盖,在派生类中新增的虚函数Fun3被加入到了先继承的基类B1的虚表中,系统这样做的目的是在调用Fun3时方便查找,只需取该对象的前四个字节拿到虚表指针即可访问。

当我们将基类B1中的Fun1和Fun2去掉virtual关键字,声明为一般的成员函数,而不是虚函数,再运行该程序,发现派生类对象d的大小变成了16个字节,还是通过画图我们来see一see它的对象模型:
这里写图片描述
变成了这样一个造型,虽然我们在派生类中先继承B1,在继承B2,但是系统还是将B2对应的部分放在派生类对象的最前面,原因可想而知,带有虚函数的类中前四个字节一般放的都是虚表指针,方便查找虚表,因此可以认为虚基类的优先级比一般基类的优先级要高。


菱形继承

class B
{
public:
    virtual void Fun1()
    {
        cout << "B::Fun1()" << endl;
    }
    virtual void Fun2()
    {
        cout << "B::Fun2()" << endl;
    }
public:
    int _b;
};
class C1:public B
{
public:
    virtual void Fun1()
    {
        cout << "C1::Fun1()" << endl;
    }
    virtual void Fun3()
    {
        cout << "C1::Fun3()" << endl;
    }
public:
    int _c1;
};
class C2 :public B
{
public:
    virtual void Fun2()
    {
        cout << "C2::Fun2()" << endl;
    }
    virtual void Fun4()
    {
        cout << "C2::Fun4()" << endl;
    }
public:
    int _c2;
};
class D :public C1,public C2
{
public:
    virtual void Fun1()
    {
        cout << "D::Fun1()" << endl;
    }
    virtual void Fun3()
    {
        cout << "D::Fun3()" << endl;
    }
    virtual void Fun4()
    {
        cout << "D::Fun4()" << endl;
    }
    virtual void Fun5()
    {
        cout << "D::Fun5()" << endl;
    }
public:
    int _d;
};
typedef void(*PVFT)();
void PrintFun(C1& c)
{

    PVFT *pVFT = (PVFT*)(*(int*)&c);
    while (*pVFT)
    {
        (*pVFT)();
        pVFT++;
    }
    cout << endl;
}
void PrintFun(C2& c)
{

    PVFT *pVFT = (PVFT*)(*(int*)&c);
    while (*pVFT)
    {
        (*pVFT)();
        pVFT++;
    }
    cout << endl;
}
int main()
{
    D d;
    cout << sizeof(d) << endl;

    d.C1::_b = 0;
    d._c1 = 1;
    d.C2::_b = 2;
    d._c2 = 3;
    d._d = 4;

    C1& c1 = d;
    PrintFun(c1);

    C2& c2 = d;
    PrintFun(c2);
}

这里写图片描述

在该菱形继承中,C1将基类B中的Fun1重写,并新增加了虚函数Fun3;C2将基类B中的Fun2重写,并增加了Fun4,派生类D继承C1和C2,并重写Fun1、3、4,增加Fun5,因此两张虚表都被改写,Fun5被加入第一张虚表,目的是在调用Fun5时方便查找。


虚拟继承

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!
这里写图片描述

猜你喜欢

转载自blog.csdn.net/shidantong/article/details/80424618