多重继承

来源:互联网 发布:js 编辑器 编辑:程序博客网 时间:2024/04/29 08:18

多重继承

在面向对象的领域中,对于多重继承的意义、是否需要在C++引入是有争论的(是的!多重继承在纯粹的对象思想看来有些怪异)。基本上撇开对于它的意义的探究,在下面着重阐述的是C++中多重继承的实现技术,以解释一些看来不易理解的地方。

考虑一个类C同时继承类A和类B。那么一个c对象(C的实例)将既包含一个a对象(A的实例),也包含一个b对象(B的实例)。我们知道,在单继承情况下,继承对象是在基类对象上自然扩张的,因为不能破坏基类对象的空间布局。那么多重继承立刻就带来了一个问题:c对象是在哪个子对象(ab)的基础上扩张而来?显然无论选择的是a,还是b,都存在对于另一个子对象的上溯造型的“指针调整”问题。设pc指向对象cpb是一个B的指针,执行:

pb = pc;

那么pb将指向对象b。如果c不是从b扩张而来,这势必要求在赋值的时候做出“修正”(需要计算出子对象bc中的相对偏移量),而不是pcpb的直接拷贝。结果,pbpc的值不再是相同的。当调用基类的成员函数时,可能会遇到同样的情况。设fb()B的一个成员函数,执行:

c.fb();

那么在调用fb()函数之前,压入栈中的所谓this指针应该指向对象b。结果再次(隐含)发生了指针调整的过程。

如果c对象既不选择从a,也不选择从b扩张而来,那么这只会增加指针调整的次数:无论上溯到a还是b,都需要进行指针调整。这不是高效的做法。

根据常见的做法,多重继承语法中基类出现的先后次序,决定对象布局中子对象排列的先后次序。例如在C的继承声明中,A在前,B在后,那么在c对象布局中,a在前,b在后(再后是c的成员数据)。

作为一般性的结论,对于多重继承下的第二个以及后面的基类,当发生上溯造型时都需要进行指针调整。而当上溯到第一个基类对象时则不需要调整。例如AC所继承的第一个基类,当执行:

pa = pc; 

或者:

c.fa();

其中pa是一个A的指针而fa()A的成员函数,那么是不用进行指针调整的,就像单继承下的情况一样。

但是多重继承最困难的地方,在于对虚机制的支持。作为个人体会,我学习多重继承不久就有一个不成文的想法,但很快被否定了。尽管这个想法是行不通的,但我想讨论一下还是能够加深对于问题的认识。下面来看一个并不复杂的例子,假设类A有虚函数,其虚函数表(部分)如下:

slot3 – A::fm()

slot4 – A::fn()

B的虚函数表(部分)如下:

slot2 – B::fx()

slot3 – B::fy()

C仍然是从AB继承的,并且覆盖了Afm()方法和Bfy()方法。

我的理解是,类C将拥有一个更大的虚函数表,就象单继承下的虚函数表“扩张”一样。这个虚函数表中将至少包含四项分别指向C::fm()A::fn()B::fx()C::fy()的函数指针。但是我们马上就注意到虚函数表项(slot)安排的冲突。在类A的虚函数表中,函数fm()占据第三个表项(slot3);而在类B中,函数fy()占据相同的位置。这点不难理解:毕竟在多重继承发生以前,不同类的虚函数表是各自安排的、彼此没有影响的,这样的“冲突”是会大量存在的。一个可能的解决办法是重新安排各个基类虚函数表的布局(受到了多重继承的影响)。例如,简单起见,我们让A的虚函数表不变,而调整B的虚函数表如下:

slot2 – B::fx()

slot5 – B::fy()

好了,通过移动有冲突的表项到不会发生冲突的位置,我们让冲突“消失”了。现在AB的虚函数表可以“叠合”在一起且彼此不会覆盖对方的表项,成为大的C的虚函数表的一部分。经过调整后C的虚函数表(部分)如下:

slot2 – B::fx()

slot3 – C::fm()

slot4 – A::fn()

slot5 – C::fy()

我称这种调整的手段,为虚函数表间的“调和”。如果另外有一个H类从BEF继承,就可能发现上述的“调和”还不够(冲突再次发生),再次进行更大范围的“调和”,直到使所有的多重继承都满足。

但是以这样的方式解决问题让人感到恐慌,因为多重继承的关系竟要求回过头来调整基类的虚函数表,实在有悖于常理,即使理论上是可行的。同时,这是极其拙劣、低效的做法,会导致类的虚函数表膨胀的很大,而且其中有很多“哑”表项。例如前面的类B,其第三,第四项是哑表项(不被使用),因为B“考虑”到有其它类(如A)的虚函数表使用这两项。另外,在上溯造型到第二个或以后的子对象时,还可能要顺带设置v-ptr指向多重继承后大的虚函数表。我自己也不相信这种做法会被实际采用,所以多重继承的虚机制实现对我来说长期是个谜。

但是问题究竟出在哪里?更好的做法是什么?原来,一个对象(如C的实例)可以关联多个虚函数表(多重继承下),没有必要强制一个对象只能关联到一个虚函数表(这迫使一个大的、统一的虚函数表的想法出现,受到单继承的影响)。经过多重继承后的类C可以认为有二个虚函数表,分别是:

slot2 – B::fx()

slot3 – C::fy()

slot3 – C::fm()

slot4 – A::fn()

前者相当于B的虚函数表的“覆盖”版本(部分)。后者相当于A的覆盖版本(部分),同时也是C“自己”的虚函数表。

对于c中的子对象b来说,它与一个单独的B的实例没有什么两样。它含有v-ptr,并且指向的虚函数表,是B的覆盖版本。一般地,对于第二个和后面的子对象,它们的v-ptr分别指向所属类的虚函数表或者其覆盖版本(如果有虚函数表或者其虚函数被覆盖的话)。对于第一个子对象,就象单继承的情形那样,与继承类对象(如c)“合用”同一个虚函数表。

很好,这样就摆脱了那个虚函数表“调和”的噩梦,基类再也不用担心受到多重继承的影响了,一切恢复了它们的原样。但是,前面困扰我们的指针调整问题将会再次出现,当上溯到第二或后面的子对象,并且调用被覆盖的方法时。例如执行:

pb = pc;

然后执行:

pb->fy();

那么由于fy()被覆盖,所以实际执行的应该是C::fy()。但是对于C::fy()的调用需要c对象的地址,只有b的地址是不能满足的。然而传递给C::fy()的,只有b的地址,怎么办?

通过所谓的Thunk技术可以地解决这个问题。此时,虚函数表中的对应fy()的表项填充的是“准”C::fy()入口。之所以说是准入口,是因为它做了以下工作:调整this指针,使之准确指向c;然后跳到C::fy()的入口执行。一切OK,多态得到了正确的贯彻。

我们看到,指针调整始终是多重继承挥之不去的问题。这种表面的、背后的不断的调整操作,正揭示了多重继承的困难和不自然。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 孕妇感冒后鼻涕带血口腔发炎怎么办 宝宝出生17天睡眠不安稳怎么办 月子里宝宝睡觉老是睡不安稳怎么办 孕妇晚期咳嗽鼻涕黄咽喉痛怎么办 狗狗流脓鼻涕拉稀没食欲怎么办 孕妇眼睛充血很快就有眼屎怎么办 婴儿的眼睛流泪生眼屎怎么办呀 刚出生的婴儿眼睛有眼屎怎么办 25天的婴儿鼻子有鼻屎不通怎么办 一个月的宝宝好多鼻屎怎么办 隆鼻取线的时候好多鼻屎怎么办 小孩流浓鼻涕怎么办最简单方法 小孩流黄鼻涕怎么办最简单方法 一岁八个月宝宝鼻涕和痰多怎么办 空调铜管过不了预埋管的弯头怎么办 如果朝鲜和韩国打起来中国怎么办 寄信时不知道对方的邮编怎么办 地下钱庄转账后银行户被冻结怎么办 老师遇到素质极差的垃圾学生怎么办 验证码忘了手机号也换了怎么办 手机上的验证码忘了怎么办 进入医联网的验证码忘了怎么办 育碧换电脑了无法同步云存档怎么办 刺客信条起源育碧需要激活码怎么办 电脑连不上网怎么办wifi可以用 电脑登录账号密码错误锁定了怎么办 白色T恤衫上沾上黑色的黄油怎么办 家教遇到成绩好的学生该怎么办 跟越南人离婚孩子中国户口9怎么办 老婆是个越南人至今没户口怎么办 等离子屏z板链接处排线打火怎么办 等离子自动调焊的成形不好怎么办 村里内村道路中间被抢占了怎么办 华为换电池之后卡没反应怎么办 汽车钥匙换电池后没反应怎么办 汽车解锁换电池后没反应怎么办 包裹显示待收件人向海关申报怎么办 在越南签证被公安扣了怎么办 酷派手机收不到验证码怎么办 苹果想把图片上的字盖上怎么办 婴儿自己把眼珠子抠红了怎么办