C++中的虚拟继承(不包含虚函数)

虚拟继承是C++编程语言里的一种语法,使得派生类如果继承基类多次,但只有一份基类的拷贝在派生类对象中。

虚拟单继承

#include<iostream>
using namespace std;
class Base//定义一个基类
{
public:
    void BaseTest()//在基类中定义一个函数
    {
        cout << "Base::BaseTest()" << endl;//让这个函数打印自己的作用域及函数名
    }
    int _base;//在基类中定义一个数据成员
};
class Derive : virtual public Base//定义一个派生类虚拟公有继承自基类
{
public:
    void DeriveTest()//在派生类中定义一个函数
    {
        cout << "Derive::DeriveTest()" << endl;//让这个函数打印自己的作用域及函数名
    }
    int _derive;//定义一个派生类自己的数据成员
};
int main()
{
    Derive d;//创建派生类对象
    cout << sizeof(d) << endl;//打印出派生类所占内存大小
    d._base = 1;//通过派生类改变继承自Base类的数据成员
    d._derive = 2;//通过派生类改变它自己的数据成员
    return 0;
}

程序的运行结果为:
这里写图片描述
通过结果我们得知,通过派生类创建的对象d的大小为12个字节,如果是普通的单继承,那通过派生类创建的对象大小应该为8字节,所以我们分析得出,d对象所在的内存中除了它来自基类的数据成员和它自己的数据成员外,还有4个字节存放了程序所要使用的数据,那么这些数据是什么呢?我对该程序进行调试,打开内存窗口,找出派生类对象d所在的地址:
这里写图片描述
因为我们在程序中执行了d._base=1; d._derive=2;所以可以知道在这里0x012FF7D0就是继承自基类base的数据成员的地址,0x012FF7CC就是派生类自己的数据成员的地址,这时候发现在派生类对象起始的四个字节依然存放了数据,但是这并不是我存放的,是程序自己存放的,为了直到这个数据到底代表着什么,我以地址的形式继续向深层探索它所指向的内存:
这里写图片描述
我们可以看到在它的前四个字节里,保存的数值为0;向后四个字节,保存的数值为8,查阅资料得知,这里的8代表的是继承自Base类的数据成员相对于Derive类起始位置的偏移量。
●根据分析,我们可以简单的画出虚拟单继承时的派生类对象模型:
这里写图片描述

结论在虚拟单继承时,在派生类对象的头四个字节会生成一个指针,这个指针指向与该派生类的基类相关联的偏移量表格。


●假设有一个名为Base的类,我们定义一个C1类让他虚拟单继承自Base类,再定义一个Derive类让它虚拟单继承自C1类,那么这个Derive类的对象模型又会是什么样呢?
编写代码先定义一个Base类,先让C1类公有虚拟继承自Base类,再定义Derive类,让它也公有虚拟继承自C1类:

#include<iostream>
using namespace std;
class Base//定义一个基类
{
public:
    void BaseTest()//在基类中定义一个函数
    {
        cout << "Base::BaseTest()" << endl;//让这个函数打印自己的作用域及函数名
    }
    int _base;//在基类中定义一个数据成员
};
class C1 :virtual public Base//定义一个类让他公有虚拟继承自Base类
{
public:
    void C1Test()//在类中定义一个函数
    {
        cout << "C1::C1Test()" << endl;//让这个函数打印自己的作用域及函数名
    }
    int _c1;//在类中定义一个数据成员
};
class Derive :virtual public C1//定义一个类Derive让它公有虚拟继承自C1类
{
public:
    void DeriveTest()//在类中定义一个函数
    {
        cout << "DeriveTest()" << endl;让这个函数打印自己的作用域及函数名
    }
    int _derive;//在类中定义一个数据成员
};


int main()
{
    Derive d;//创建派生类对象
    cout << sizeof(d) << endl;//打印派生类的大小
    d._base = 1;//通过派生类对象改变继承自Base的数据成员
    d._c1 = 2;//通过派生类对象该变继承自C1类的数据成员
    d._derive = 3;//通过派生类对象改编它自己的数据成员
    return 0;
}

运行结果:
这里写图片描述
通过运行结果可以得知,派生类对象的大小为20字节。当我们通过普通继承时,经过这样的继承方式继承下来的派生类对象的大小应该是12字节,但是经过虚拟继承,比普通继承多出了8个字节,调试该程序,截取出派生类对象d所在的内存地址:
这里写图片描述
因为在程序中执行了d._base=1; d._c1=2; d._derive=3;所以可以得知0x006FFA3C就是继承自Base类的数据成员所在的地址,0x006FFA44就是继承自C1类的数据成员的地址,0x006FFA38就是Derive类自己的数据成员所在的地址。在派生类对象里,有两个位置的数据我们不了解是用来做什么的,我们把这两个数据当作指针,分别来探索它们指向的位置:
●查看指针0x0012CC80所指向的位置:
这里写图片描述
经过分析,可以看出在Derive类对象的起始位置的地址指向了一个表格,这个表格前四个字符保存的是该表格地址相对于Derive类对象起始位置的偏移量0,向后四个字节存放的是继承自Base类模型的数据成员的地址相对于Derive类的起始位置的偏移量8,再向后四个字节存放的是继承自C1类模型的未知指针及数据成员的地址相对于Derive类对象起始位置的偏移量12。
●查看指针0x0012D180所指向的位置:
这里写图片描述
在Derive类对象的内存中也存在C1类的模型,0x0012D180就是C1类模型的头四个字节,经过分析,可以看出它里面保存着继承自Base类的数据成员相对于Derive类对象中C1类模型的起始位置的偏移量-4。
Derive类对象的模型为:
这里写图片描述
结论:当Derive类作为派生类虚拟单继承一个C1类时(C1类也是经过虚拟单继承继承自一个普通的Base类),Derive类对象的头四个字节会生成一个指针,这个指针指向一个与C1类和Base类相关的偏移量表格,并且Derive类不会将Base类的数据成员看作C1类的数据成员,而且C1类模型的偏移量表格也会被Derive类继承。在C1类模型的偏移量表格中关于Base类的偏移量也会进行相应的更新。


多重虚拟继承

#include<iostream>
using namespace std;
class Base1//定义Base1类
{
public:
    int _base1;//定义一个数据成员
};
class Base2//定义Base2类
{
public:
    int _base2;//定义一个数据成员
};
//定义Derive类;让Derive类公有虚拟继承自Base1类和Base2类
class Derive :virtual public Base1, virtual public Base2
{
public:
    int _derive;//定义一个数据成员
};
int main()
{
    Derive d;//创建Derive类的对象
    cout << sizeof(d) << endl;//打印Derive类对象的大小
    d._base1 = 1;//通过派生类对象改变继承自Base1类数据成员的值
    d._base2 = 2;//通过派生类对象改变继承自Base2类数据成员的值
    d._derive = 3;//通过派生类对象改变他自己的数据成员
    return 0;
}

运行结果:
这里写图片描述
通过运行结果可以得知,派生类对象所占大小为16字节。调试该程序,查看派生类对象所在的内存:
这里写图片描述
因为在程序里我们执行了d._base1=1; d._base2=2; d._derive=3;所以可以看出0x00F699C就是继承自Base1类的数据成员的地址,0x00F6F9A0就是继承自Base2类的数据成员的地址,0x00F6F998就是Derive类自己的数据成员的地址,0x00F6F994指向的内存里存放着一个指针,这个指针指向与Base1类和Base2类相关的偏移量表格。我们打开该表格所在的地址:
这里写图片描述
经过分析,可以简的画出Derive类的对象模型:
这里写图片描述


菱形虚拟继承

因为类的对象里并不会包含普通函数的信息,所以从这里开始,例子中将不再定义普通函数。

#include<iostream>
using namespace std;
class Base//定义一个base类
{
public:
    int _base;//定义一个数据成员
};
class C1:virtual public Base//定义C1类虚拟继承自Base类
{
public:
    int _c1;//定义一个数据成员

};
class C2 : virtual public Base//定义C2类虚拟继承自Base类
{
public:
    int _c2;//定义一个数据成员
};
//定义Derive类公有虚拟多继承自C1类和C2类
class Derive : virtual public C1, virtual public C2
{
public:
    int _derive;//定义一个数据变量
};
int main()
{
    Derive d;//创建Derive类对象
    cout << sizeof(d) << endl;//打印Derive类对象的大小
    d._base = 1;//通过Derive类对象改变Base类的数据成员
    d._c1 = 2;//通过Derive类对象改变C1类对象的数据成员
    d._c2 = 3;//通过Derive类对象改变C2类对象的数据成员
    d._derive = 4;//通过Derive类对象改变它自己的数据成员
    return 0;
}

运行结果:
这里写图片描述
用虚拟菱形继承的方式没有再出现我们用普通菱形继承时访问对象不明确的错误,这就说名虚拟继承可以解决普通菱形继承二义性的问题,通过运行结果,得知Derive类对象的大小为28字节,对程序进行调试,取出Derive类对象所在的地址:
这里写图片描述
因为在程序中执行了d._base=1; d._c1=2; d._c3=3; d._derive=4;所以可以得知0x00EFF7AC就是继承自Base类的数据成员的地址,0x00EFF7B4就是继承自C1类的数据成员的地址,0x00EFFBC就是继承自C2类的数据成员的地址,在虚拟菱形继承的对象模型里,只有唯一的Base类成员。经过分析,0x00EFF7A4就是Derive类模型关于Base类数据成员和C1类模型和C2类模型的偏移量列表的地址,0x00EFF7B0就是C1类模型关于Base类数据成员的偏移量列表的地址,0x00EFF7BC就是C2类模型关于Base类数据成员的偏移量列表的地址。
查看Derive类的偏移量列表:
这里写图片描述
查看C1类模型的偏移量列表:
这里写图片描述
查看C2类模型的偏移量列表:
这里写图片描述
所以我们可以简单画出菱形虚拟继承下,Derive类的对象模型:
这里写图片描述
通过上面的例子,我们发现虚拟继承是令某个类做出声明,承诺愿意共享它的基类,共享的基类称为虚基类,在这种情况下,不管虚基类在继承体系中被继承过多少次,在派生类中都只包含唯一一个共享的虚基类子对象。并且可以得知他们是通过各自的偏移量列表来实现共享的。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值