C++继承(三)通过菱形继承看virtual继承

什么事菱形继承,前面2节也没有讲到,其实,在常用的继承方式中,看到了单继承和多继承,其实菱形继承就是单继承和多继承的组合:
C++继承(三)通过菱形继承看virtual继承_第1张图片

class Base
{
public:
    Base()
    {
        cout << "Base::Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base::Base()" << endl;
    }
private:
    int _b;
};

class C1:public Base
{
public:
    C1()
    {
        cout << "C1::C1()" << endl;
    }
    ~C1()
    {
        cout << "~C1::C1()" << endl;
    }
private:
    int _c1;
};

class C2 :public Base
{
public:
    C2()
    {
        cout << "C2::C2()" << endl;
    }
    ~C2()
    {
        cout << "~C2::C2()" << endl;
    }
private:
    int _c2;
};

class D:public C1, public C2
{
public:
    D()
    {
        cout << "D::D()" << endl;
    }
    ~D()
    {
        cout << "~D::D()" << endl;
    }
private:
    int _d;
};

int main()
{
    cout << sizeof(Base) << endl;
    cout << sizeof(C1) << endl;
    cout << sizeof(C2) << endl;
    cout << sizeof(D) << endl;
    return 0;
}

C++继承(三)通过菱形继承看virtual继承_第2张图片
从结果上来看,它的模型是:
C++继承(三)通过菱形继承看virtual继承_第3张图片
但是,这并不是我们想要的菱形继承的模型,我们可以看到在D中有2个Base类中的int b;在调用时要标明它是从C1和C2中哪个继承的,造成了菱形继承数据冗余问题。所以,就有了virtual继承;

1、虚拟继承

虚拟继承就是为了解决类似于菱形寂继承中出现数据冗余的问题而诞生的;
虚拟继承和飞虚拟继承就是前面两个图所显示的模型;

那么虚拟继承是怎么解决数据冗余的问题的:
在类中不加数据时:

class Base
{
public:
    Base()
    {
        cout << "Base::Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base::Base()" << endl;
    }
};

class C1:virtual public Base
{
public:
    C1()
    {
        cout << "C1::C1()" << endl;
    }
    ~C1()
    {
        cout << "~C1::C1()" << endl;
    }
};

class C2 :virtual public Base
{
public:
    C2()
    {
        cout << "C2::C2()" << endl;
    }
    ~C2()
    {
        cout << "~C2::C2()" << endl;
    }
};

class D:public C1, public C2
{
public:
    D()
    {
        cout << "D::D()" << endl;
    }
    ~D()
    {
        cout << "~D::D()" << endl;
    }
};

int main()
{
    cout << sizeof(Base) << endl;
    cout << sizeof(C1) << endl;
    cout << sizeof(C2) << endl;
    cout << sizeof(D) << endl;
    return 0;
}

结果是:
C++继承(三)通过菱形继承看virtual继承_第4张图片
我们从结果开始分析:

class Base
{
public:
    Base()
    {
        cout << "Base::Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base::Base()" << endl;
    }
};

在基类中,因为没有其他的成员,只有构造函数和析构函数,所以

sizeof(Base) = 1;

为什么会是1,明明是空的class啊 为啥不是0了?
因为编译器中要给每个对象不同的地址,所以为了区分类中的每个对象,所以编译器给分配一个空间;
还是上面的代码:只是在main函数中创建了两个对象,看看两个对象的地址如图:
C++继承(三)通过菱形继承看virtual继承_第5张图片
b1,b2的地址是不同的,所以必须开辟 一个空间来区别它们的地址区别;
下面看看,对于virtual继承的C1和C2中的函数模型,C1和C2是一样的,所以看看C1的模型就可以了


class C1:virtual public Base
{
public:
    C1()
    {
        cout << "C1::C1()" << endl;
    }
    ~C1()
    {
        cout << "~C1::C1()" << endl;
    }
    //int _c1;
};

class C2 :virtual public Base
{
public:
    C2()
    {
        cout << "C2::C2()" << endl;
    }
    ~C2()
    {
        cout << "~C2::C2()" << endl;
    }
};

我们在C1对象下生成一个c1对象来查看:
为什么C1和C2都是没有成员的为什么有4个字节的空间占有,virtual继承其实是这里4个字节的主要原因,因为是virtual继承,所以编译器会给C1和C2生成一个叫虚表的东西,它里面存的是一个指针:
C++继承(三)通过菱形继承看virtual继承_第6张图片
我们在内存中查看C1的地址时,会发现它的内存地址指向的好像也是一个地址:
C++继承(三)通过菱形继承看virtual继承_第7张图片
我们进去看时,发现他确实是一个地址,因此,我们可以知道他为什么占4个字节了,因为他是一个指针;
但是,它指向的这个空间又是干什么的?
从上图我们看到的是,它美誉成员变量时,在内存2中,有两个偏移量一个是0,一个是4;
其中,0表示的而是派生类C1相对于虚表的偏移量,因为虚表就是在C1中,所以就相当于自己对于自己的偏移量就是0;
4表示的是:派生类对象相对于虚表的偏移量,在上面代码中C1并没有成员变量,所以相对偏移量只有一个C1相对于自己偏移量所占的空间,就是4个;

class Base
{
public:
    Base()
    {
        cout << "Base::Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base::Base()" << endl;
    }
    int _pub = 30;
};

class C1:virtual public Base
{
public:
    C1()
    {
        cout << "C1::C1()" << endl;
    }
    ~C1()
    {
        cout << "~C1::C1()" << endl;
    }
    int _c1 = 10;
    int _c2 = 20;
};

当我们在C1中定义2个变量在基类中定义一个变量,我们在来看看C1的地址和虚表结构:
C++继承(三)通过菱形继承看virtual继承_第8张图片
C++继承(三)通过菱形继承看virtual继承_第9张图片
我们再C1中定义 2个变量,在基类中定义了1个变量,在C1的地址中可以看到每个值得大小,在虚表中我们只看到看偏移量的改变;
所以在虚表中第一个一般为0表示派生类对于自己的偏移量,而第二个值不定,表示的是基类的成员对于派生类的偏移量(PS,我们在派生类中定义了2个加上0的偏移量刚刚是12个也就是c;所以没问题;
C1和C2一样的都时virtual继承,它俩一毛一样;

下面看看大招D:

class Base
{
public:
    Base()
    {
        cout << "Base::Base()" << endl;
    }
    ~Base()
    {
        cout << "~Base::Base()" << endl;
    }
    int _pub = 10;
};

class C1:virtual public Base
{
public:
    C1()
    {
        cout << "C1::C1()" << endl;
    }
    ~C1()
    {
        cout << "~C1::C1()" << endl;
    }
    int _c1 = 20;
};

class C2 :virtual public Base
{
public:
    C2()
    {
        cout << "C2::C2()" << endl;
    }
    ~C2()
    {
        cout << "~C2::C2()" << endl;
    }
    int c2 = 30;
};

class D:public C1, public C2
{
public:
    D()
    {
        cout << "D::D()" << endl;
    }
    ~D()
    {
        cout << "~D::D()" << endl;
    }
    int d = 40;
};

int main()
{
    //Base b1, b2;
    D d;
    //cout << sizeof(base) << endl;
    //cout << sizeof(c1) << endl;
    //cout << sizeof(c2) << endl;
    //cout << sizeof(d) << endl;
    return 0;
}

C++继承(三)通过菱形继承看virtual继承_第10张图片
在代码内部我在每个class中添加了成员并给他们赋值;
又内存上显示的我们可以看出2,4,5,6是我们成员变量的值,分别是C1,C2,DB中的值,而1,3中的值好像又是地址
先看第一行的地址:
C++继承(三)通过菱形继承看virtual继承_第11张图片
第一行还是和原来的一样是虚表相对于自己的偏移量,第二个是偏移量是20哦所以它指的是值为0a的那一行,是10就是D中的值,就是基类相对于派生类的偏移量;
第二行:
C++继承(三)通过菱形继承看virtual继承_第12张图片

现在就看0c所对应的值->1e,它是30,是C2的成员变量的值,所以说明第二个地址表示的C2相对于自己的偏移量中的偏移,所以我们可以用一幅图来表示他们在地址空间中的存放:
C++继承(三)通过菱形继承看virtual继承_第13张图片
有人可能会问,在C1和C2中有两个虚表,如果继承下来加上D自己的应该是3个虚表,而在D中只有2个虚表,其实这样想的人忘记了我们这是虚拟继承,处理的就是数据冗余的问题,因为C1和C2都是继承B中的东西,我们在virtual继承中用一个虚表就可以了。
有任何问题,欢迎大家指正,一起进步。

你可能感兴趣的:(C/C++)