【转】C++多继承的细节
这几天写的程序应用到多继承。
以前对多继承的概念非常清晰,可是很久没用就有点模糊了。重新研究一下,“刷新”下记忆。
假设我们有下面的代码:
#include <stdio.h>
class A
{
private:
char data;
public:
A(){data = 'A';}
virtual void Show(){printf("A/n");};
virtual void DispA(){printf("a/n");};
};
class B
{
private:
int data;
public:
B(){data = 'B';}
virtual void Show(){printf("B/n");};
virtual void DispB(){printf("b/n");};
};
class C
{
private:
char data;
public:
C(){data = 'C';}
virtual void Show(){printf("C/n");};
virtual void DispC(){printf("c/n");};
};
class D : public A, public B, public C
{
public:
char data;
public:
D(){data = 'D';}
virtual void Show(){printf("D/n");};
virtual void DispD(){printf("d/n");};
};
class E : public D
{
private:
char data;
public:
E(){data = 'E';}
virtual void Show(){printf("E/n");};
virtual void DispE(){printf("e/n");};
};
int main()
{
D *d = new D;
A *a = (A*)d;
B *b = (B*)d;
C *c = (C*)d;;
d->Show();
a->Show();
b->Show();
a->DispA();
b->DispB();
d->DispD();
D *d1 = (D*)a;
d1->Show();
d1->DispD();
D *d2 = (D*)b;
d2->Show();
d2->DispD();
char x = d->data;
return 0;
}
每个类都有两个虚拟函数Show()和DispX()。类A,B,C是基本类,而D是多继承,最后E又继承了D。那么对于类E,它的内存映像是怎样的呢?为了解答这个问题,我们回顾一下基本类的内存映像:
+ --------------+ <- this
+ VTAB +
+ --------------+
+ +
+ Data +
+ +
+ --------------+
如果一个类有虚拟函数,那么它就有虚函数表(VTAB)。类的第一个单元是一个指针,指向这个虚函数表。如果类没有虚函数,并且它的祖先(所有父类)均没有虚函数,那么它的内存映像和C的结构一样。所谓虚函数表就是一个数组,每个单元指向一个虚函数地址。
如果类Y是类X的一个继承,那么类Y的内存映像如下:
+ --------------+ <- this
+ Y 的 VTAB +
+ --------------+
+ +
+ X 的 Data +
+ +
+ --------------+
+ +
+ Y 的 Data +
+ +
+ --------------+
Y的虚函数表基本和X的相似。如果Y有新的虚函数,那么就在VTAB的末尾加上一个。如果Y重新定义了原有的虚函数,那么原的指针指向新的函数入口。这样无论是内存印象和虚函数表,Y都和X兼容。这样当执行 X* x = (Y*)y;之后,x可以很好的被运用,并且可以享受新的虚拟函数。
现在看多重继承:
class D : public A, public B, public C
{
....
}
它的内存映像如下:
+ --+ -----------------+ 00H <- this
+ + D 的 VTAB +
+ A + -----------------+ 04H
+ + A 的 数据 +
+ --+ -----------------+ 08H
+ + B 的 VTAB' +
+ B + -----------------+ 0CH
+ + B 的 数据 +
+ --+ -----------------+ 10H
+ + C 的 VTAB' +
+ C + -----------------+ 14H
+ + C 的 数据 +
+ --+ -----------------+ 18H
+ D + D 的 数据 +
+ --+ -----------------+
(因为对齐于双字,A~D的数据虽然只是一个char,但需要对齐到DWORD,所以占4字节)
对于A,它和单继承没有什么两样。B和C被简单地放在A的后面。如果它们虚函数在D中被重新定义过(比如Show函数),那么它们需要使用新的VTAB,使被重定义的虚函数指到正确的位置上(这对于COM或类似的技术是至关重要的)。最后,D的数据被放置到最后面。
对于E的内存映像问题就可以不说自明了。
下面我们看一下C++是如何处理
D *d;
......
B *b = (B*)d;
这样的要求的。设置断点,进入反汇编,你可以看到如下的汇编代码:(因为UBB关系,将方括号替换成了大括号。看上去有点别扭)
B *b = (B*)d;
00401062 cmp dword ptr {d},0
00401066 je main+73h (401073h)
00401068 mov eax,dword ptr {d}
0040106B add eax,8
0040106E mov dword ptr {ebp-38h},eax
00401071 jmp main+7Ah (40107Ah)
00401073 mov dword ptr {ebp-38h},0
0040107A mov ecx,dword ptr {ebp-38h}
0040107D mov dword ptr {b},ecx
从上述汇编代码可以看出:如果源(这里是d)是NULL,那么目标(这里是b)也将被置为NULL,否则目标将指向源的地址并向下偏移8个字节,正好就是上图所示B的VTAB位置。至于为什么要用ebp-38h作缓存,这是编译器的算法问题了。等以后有时间再研究。
接下来看一个比较古怪的问题,这个也是我写这篇文章的初衷:
根据上面的多继承定义,如果给出一个类B的实例b,我们是否可以求出D的实例?
为什么要问这个问题。因为存在下面的可能性:
class B
{
...
virtual int GetTypeID()=0;
...
};
class D : public A, public B, public C
{
...
virtual int GetTypeID(){return 0;};
...
};
class Z : public X, public Y, public B
{
...
virtual int GetTypeID(){return 1;};
...
};
void MyFunc(B* b)
{
int t = b->GetTypeID();
switch(t)
{
case 0:
DoSomething((D*)b); //可能吗?
break;
case 1:
DoSomething((Z*)b); //可能吗?
break;
default:
break;
}
}
猛一看很值得怀疑。但仔细想想,这是可能的,事实也证明了这一点。因为编译器了解这D和B这两个类相互之间的关系(也就是偏移量),因此它会做相应的转换。同样,设置断点,查看汇编:
D *d2 = (D*)b;
00419992 cmp dword ptr {b},0
00419996 je main+196h (4199A6h)
00419998 mov eax,dword ptr {b}
0041999B sub eax,8
0041999E mov dword ptr {ebp-13Ch},eax
004199A4 jmp main+1A0h (4199B0h)
004199A6 mov dword ptr {ebp-13Ch},0
004199B0 mov ecx,dword ptr {ebp-13Ch}
004199B6 mov dword ptr {d2},ecx
如果源(这里是b)为NULL,那么目标(这里是d2)也为NULL。否则目标取源的地址并向上偏移8个字节,这样正好指向D的实例位置。同样,为啥需要ebp-13Ch做缓存,待查。
前一段时间因为担心.NET中将interface转成相应的类会有问题。今天对C++多重继承的复习彻底消除了疑云。
【转】C++多继承的细节的更多相关文章
- Java 方法重载,方法重写(覆盖),继承等细节注意
1.方法重载(method overload)的具体规范 如果有两个方法的方法名相同,但参数不一致,那么可以说一个方法是另一个方法的重载. 一.方法名一定要相同. 二.方法的参数表必须不同,包括参数的 ...
- Java编程的逻辑 (16) - 继承的细节
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...
- ES6 class的继承使用细节
ES6 class的继承与java的继承大同小异,如果学过java的话应该很容易理解,都是通过extends关键字继承. class Animal{ constructor(color){ this. ...
- Maven之 聚合与继承 详解
说到聚合与继承我们都很熟悉,maven同样也具备这样的设计原则,下面我们来看一下Maven的pom如何进行聚合与继承的配置实现. 一.为什么要聚合? 随着技术的飞速发展和各类用户对软件的要求越来越高, ...
- Maven详解之聚合与继承
说到聚合与继承我们都很熟悉,maven同样也具备这样的设计原则,下面我们来看一下Maven的pom如何进行聚合与继承的配置实现. 一.为什么要聚合? 随着技术的飞速发展和各类用户对软件的要求越来越高, ...
- 黑马程序员——OC语言 三大特性之继承
Java培训.Android培训.iOS培训..Net培训.期待与您交流! (以下内容是对黑马苹果入学视频的个人知识点总结) 三大特性之继承 (一)继承的基本用法 先建立个Animal再用Dog继承前 ...
- maven之详解继承与聚合
说到聚合与继承我们都很熟悉,maven同样也具备这样的设计原则,下面我们来看一下Maven的pom如何进行聚合与继承的配置实现. 一.为什么要聚合? 随着技术的飞速发展和各类用户对软件的要求越来越高, ...
- Maven详解 之 聚合与继承
说到聚合与继承我们都很熟悉,maven同样也具备这样的设计原则,下面我们来看一下Maven的pom如何进行聚合与继承的配置实现. 一.为什么要聚合? 随着技术的飞速发展和各类用户对软件的要求越来越高, ...
- Maven聚合和继承的详细解释
说到聚合与继承我们都非常熟悉,maven相同也具备这种设计原则.以下我们来看一下Maven的pom怎样进行聚合与继承的配置实现. 一.为什么要聚合? 随着技术的飞速发展和各类用户对软件的要求越来越高. ...
随机推荐
- 配置apt-get告诉下载源
本文转自:http://blog.csdn.net/hyl1718/article/details/7915296 方法: 1.修改源地址: cp /etc/apt/sources.list /etc ...
- 【Python】安装python包时遇到"error: Microsoft Visual C++ 9.0 is required"的简答
简答 在Windows下用pip安装Scrapy报如下错误, error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall ...
- hdu 3339 In Action (最短路径+01背包)
In Action Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total S ...
- (八)Redis事务与连接操作
事务: multi # 标记一个事务块的开始.事务块内的多条命令会按照先后顺序被放进一个队列当中,最后由EXEC命令原子性地执行 exec # 执行所有事务块内的命令 descard # 取消事务,放 ...
- 进程间通讯-3(Manager)-实现数据的同时修改
Manager 可以实现列表,字典,变量,锁,信号量,事件等的数据之间的共享.Manager已经默认加锁了.控制数据不会乱. 实现了不同进程之间数据的共享,并且可以同时修改. from multipr ...
- 前端开发学习之——dom ready和window onload的区别
1.ready事件是在页面中所有DOM结构已完全加载时执行,监听的是 DomContentload 事件,初始化并解析完成时触发,不需要等待样式表.图片和 iframes 加载完,也就是说当这个事件触 ...
- 【刷题】BZOJ 4503 两个串
Description 兔子们在玩两个串的游戏.给定两个字符串S和T,兔子们想知道T在S中出现了几次, 分别在哪些位置出现.注意T中可能有"?"字符,这个字符可以匹配任何字符. I ...
- [NOI.AC省选模拟赛3.31] 附耳而至 [平面图+最小割]
题面 传送门 思路 其实就是很明显的平面图模型. 不咕咕咕的平面图学习笔记 用最左转线求出对偶图的点,以及原图中每个边两侧的点是谁 建立网络流图: 源点连接至每一个对偶图点,权值为这个区域的光明能量 ...
- POJ2826:An Easy Problem?!——题解(配特殊情况图)
http://poj.org/problem?id=2826 题目大意:给两条线,让它接竖直下的雨,问其能装多少横截面积的雨. ———————————————————————————— 水题,看题目即 ...
- [bzoj] 1040 骑士 || 基环外向树dp
原题 给出n个点n条边和每个点的点权,一条边的两个断点不能同时选择,问最大可以选多少. //图是一张基环外向树森林 是不是很像舞会啊- 就是多了一条边. 所以我们考虑一下对于一棵基环外向树,拆掉一条在 ...