由结构体对齐而引发的思考(三)——考虑虚基类时c++类对象内存情况
来源:互联网 发布:js 直接写 和 onload 编辑:程序博客网 时间:2024/06/18 07:53
一 多继承
很多时候,一个子类可能会有多个父类,比如美人鱼既是人也是鱼,能打电话的Pad等等,为了增强代码复用能力,就有了多继承,已解决有多个父类的问题。示例代码如下
class Base_A
{
public:
Base_A() :a(0x10),b(0x20)
{ }
int a;
int b;
};
class Base_B
{
public:
Base_B() :c(0x30),d(0x40)
{ }
int c;
int d;
};
class Inherit :publicBase_A, public Base_B
{
public:
Inherit() :e(0x50)
{ }
int e;
};
int _tmain(intargc, _TCHAR*argv[])
{
Inherit obj;
return 0;
}
如此这般,Inherit的对象,就能够使用从两个父类继承下来的所有数据和方法(需要考虑权限问题)。我们来看一下它的内存模型
可以看到,子类对象包含着父类的全部数据,我们再看另外一种情况
class Base_A
{
public:
Base_A() :a(0x10),b(0x20)
{ }
int a;
int b;
};
class Base_B
{
public:
Base_B() :c(0x30),d(0x40)
{ }
int c;
int d;
};
class Inherit :publicBase_B, public Base_A
{
public:
Inherit() :e(0x50)
{ }
int e;
};
int _tmain(intargc, _TCHAR*argv[])
{
Inherit obj;
return 0;
}
内存模型如下
此时我们可以得出一些简单的结论:
1 派生类放在最下面
2 多个父类的情况下,谁在上,谁在下,由继承顺序决定。
3 子类总是包含全部的父类
二 多继承中的二义性问题
比如有这么一个物种叫做狼人,它有着锋利的牙齿,恐怖的速度,还能两个腿奔跑。它可以由狼类和人类共同派生出来。但是有一个问题,就是狼类中可能会有腿的数量,牙齿的数量等等属性,恰好人类中也有腿的数量,牙齿的数量等等属性。我们知道子类会具有全部父类的所有成员。那么此时此刻,狼人对象访问腿的数量,牙齿的数量的时候,会访问哪个父类的成员呢?
这是一个不知道的问题。看你想使用谁的,使用谁的就加上谁的作用域。Oh,这简直太蠢了,示例代码如下:
class Wolf
{
public:
Wolf() :m_nWolfSomeThing(0x10)
{ }
public:
int m_nNumberOfLegs;
int m_nWolfSomeThing;
};
class Human
{
public:
Human() :m_nHumanSomeThing(0x20)
{ }
int m_nNumberOfLegs;
public:
int m_nHumanSomeThing;
};
class Werwolf :publicWolf, public Human
{
public:
Werwolf() :m_nWerwolfSomeThing(0x30)
{ }
public:
int m_nWerwolfSomeThing;
};
int _tmain(intargc, _TCHAR*argv[])
{
Werwolf obj;
//obj.m_nNumberOfLegs;//错误的,使用的不明确
obj.Human::m_nNumberOfLegs;
obj.Wolf::m_nNumberOfLegs;
return 0;
}
聪明的人已经想出了办法,就是把狼和人都有的成员抽象出来,形成一个爷爷类,比如叫做动物类,在狼类和人类的上面,形成如下图所示的情况,这太完美了。
想向中应该是这个样子
实际上是不是这样的呢?请看以下示例代码:
class Animal
{
public:
Animal() :m_nNumberOfLegs(5)//默认5条腿^o^
{ }
public:
int m_nNumberOfLegs;
};
class Wolf :publicAnimal
{
public:
Wolf() :m_nWolfSomeThing(0x10)
{ }
public:
int m_nWolfSomeThing;
};
class Human :publicAnimal
{
public:
Human() :m_nHumanSomeThing(0x20)
{ }
public:
int m_nHumanSomeThing;
};
class Werwolf :publicWolf, public Human
{
public:
Werwolf() :m_nWerwolfSomeThing(0x30)
{ }
public:
int m_nWerwolfSomeThing;
};
int _tmain(intargc, _TCHAR*argv[])
{
Werwolf obj;
return 0;
}
可惜天不遂人愿,依然是有两个腿的数量,这是因为子类对象会包含全部的父类成员。对于狼来说,自然会包含动物类中的腿的数量。对于人来说,也是如此。对于狼人来说,会同时包含狼类和人类的所有成员。故而腿的数量这个字段,在狼人对象中依然是出现两个,一份在狼中,一份在人中。这显然是不符合我们的想法的,没有办法,计算机跟不上人的思维很正常,这是典型的菱形继承问题。
三虚继承
为了解决这个问题,产生了一种叫做虚继承的机制
首先我们来单聊一下虚继承,首先虚继承是为了解决二义性的问题而产生的语法。用法是在继承之前加上一个virtual,我们来看一下最为简单的情况,下面的例子什么问题都没有解决,但是可以帮助我们理解虚继承到底做了什
class Base
{
public:
Base() :m_a(0x10)
{ }
public:
int m_a;
};
class Inherit :virtualpublic Base
{
public:
Inherit() :m_b(0x20)
{ }
public:
int m_b;
};
int _tmain(intargc, _TCHAR*argv[])
{
Inherit obj;
printf("虚继承的对象大小%d",sizeof(obj));
return 0;
}
现在我们可以猜一猜obj的大小是多大呢?你绝对不会想到。
假如这个你确实猜到了,那么后面的内容,你绝对不会猜到了,下面是obj的内存模型
我们可以看到在整个对象的开头多了一个奇怪的数据,并且神奇的是派生类数据竟然骑到了基类数据的上面,我们来解释解释它在干什么:
头4个字节实际上是一个地址即:0x095858.
我们到那里看一看
刚才的那个地址,我们称之为虚基类表指针,指向的位置存储的是一共有两个元素,分别是两个差值:
1 本类地址与虚基类表指针地址的差
2 虚基类地址与虚基类表指针地址的差
struct VirtualBase
{
int Offect1;
int Offect2;
}
这里我们着重关注第二个,它能够实现这样的事情:基类与派生类可以不挨在在一起,是通过虚基类表中的差值,从派生类就可以找到基类的数据。
我们直接看复杂一些的情况,结合上面的例子更加容易理解一些
class Base
{
public:
Base() :m_a(0x10)
{ }
public:
int m_a;
};
class Inherit_A :virtualpublic Base
{
public:
Inherit_A() :m_b(0x20)
{ }
public:
int m_b;
};
class Inherit_B :virtualpublic Base
{
public:
Inherit_B() :m_c(0x30)
{ }
public:
int m_c;
};
class Test :publicInherit_A, publicInherit_B
{
public:
Test() :m_d(0x40)
{ }
public:
int m_d;
};
int _tmain(intargc, _TCHAR*argv[])
{
Test obj;
printf("虚继承的对象大小%d",sizeof(obj));
return 0;
}
在开始之前,大家可以先猜测一下这个对象的大小是多大?本身一共4个int型,妥妥的应该是16个字节。实际上
我们可以来看一下它的内存模型:
可以看出:
从上到下的顺序是A,B,派生类,基类Base。Base类被甩到了最后,并且只有一个。Inherit_A与Inherit_B共用一个虚基类。
这个机制,无论是几个中间内一层的类,都能保证虚基类的数据只有一份,这就是虚继承解决多继承中二义性的问题。
此时此刻我们便可以总结一下:
1 进行如图所示的虚继承
编译器会把虚基类单独置于一处,派生类通过虚基类表指针指向位置存储的差值能够找到虚基类,当类似于图示的情况下的时候,使得孙子类无论从哪一条支路寻找爷爷类(虚基类),找到的都是同一个爷爷。
2 对于类对象大小,每一个虚继承的子类由于都会有一个虚基类指针,故而多一个虚继承,整个对象的大小就会比正常大4个字节。
3 虚基类实际上不需要一定放在下面,放在任何位置都可以,因为大家是通过一个差值找到的它。
- 由结构体对齐而引发的思考(三)——考虑虚基类时c++类对象内存情况
- 由结构体对齐而引发的思考(二)类对象内存模型
- 由结构体对齐而引发的思考。。。(一)
- 由getchar()函数的使用而引发的思考.
- C语言结构体对象内存对齐
- c/c++结构体、对象内存对齐
- 由数据库引发面向对象的思考
- 由“结构化编程与面象对象编程争论”引发的思考
- 由一次对arm7的中断选择寄存器(VICIntSelect)赋值而引发的思考
- C语言中的可变参数——由printf()引发的思考
- 【内存泄露】由Handler引发的内存泄漏的思考
- 【内存泄露】由Handler引发的内存泄漏的思考
- C里结构体的内存对齐
- C语言结构体的内存对齐
- 由IsPostBack引发的思考(续)
- 由JVM引发的思考_基本结构
- 由“标准C”“纯C”引发的思考
- C语言之struct大小、首地址与内存对齐—由结构体成员地址得到结构体首地址
- Zookeeper之Session和Http之Session类比学习
- 欢迎使用CSDN-markdown编辑器
- Android中将View的内容保存为图像的方法
- Linux source命令
- 规划问题的matlab求解
- 由结构体对齐而引发的思考(三)——考虑虚基类时c++类对象内存情况
- 例说数据结构&STL(九)——map
- IDRISI Andes V15.0 (交互式GIS和影像处理)
- solr详细解析
- OLTP与OLAP
- 2017-7-29
- Wooden Sticks
- cs231n 学习过程 问题记录
- [leetcode]50. Pow(x, n)@Java解题报告