多重继承地内存分布_第1页
多重继承地内存分布_第2页
多重继承地内存分布_第3页
多重继承地内存分布_第4页
多重继承地内存分布_第5页
已阅读5页,还剩22页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、实用标准文案指针的比较再以上面Bottom类继承关系为例讨论,下面这段代码会打印Equal吗?1 Bottom* b = new Bottom();2 Right* r = b;34 if (r = b)5 printf( "Equal!/n u);先明确下这两个指针实际上是指向不同地址的,r指针实际上在b指针所指地址上偏移8字节,但是,这些C+内部细节不能告诉C+程序员,所以C+编译器在比较r和b时,会把r减去 8字节,然后再来比较,所以打印出的值是"Equal".多重继承首先我们先来考虑一个很简单(nonvirtual)的多重继承。看看下面这个C+类层次结构。

2、1 class Top2 3 public :4 int a;5 );667891012131415161718181920212224class Left: public Top(public :intb;);class Right: public Top(public :intc;);class Bottom : public Left, public Right(public :intd;);用UML表述如下:Bottom注意到Top类实际上被继承了两次,(这种机制在Eiffel中被称作a属性repeated inheritanee) ,这就意味着在一个bottom对象中实际上有两个(a

3、ttributes,可以通过 bottom.Left:a 和 bottom.Right:a 访问)。那么Left、Right、Bottom在内存中如何分布的呢?我们先来看看简单的Left和Right内存分布:Right类的布局和Left是一样的,因此我这里就没再画图了。刺猬注意到上面类各自的第一个属性都是继承自Top类,这就意味着下面两个赋值语句1 Left* left = new Left ();2 Top* top = left;left和top实际上是指向两个相同的地址,我们可以把 Left对象当作一个Top对象(同样也可以把Right对象当Top对象来使用)。但是Botom对象呢? G

4、CC是这样处理的:BottomLeft;:Top;1aLeft:bRight:;Top:aRight :cBottomed但是现在如果我们upcast 一个Bottom指针将会有什么结果?1 Bottom* bottom = new Bottom。2 Left* left = bottom;这段代码运行正确。这是因为GCC选择的这种内存布局使得我们可以把Bottom对象当作Left对象,它们两者(Left部分)正好相同。但是,如果我们把Bottom对象指针upcast到 Right对象呢?Bottom中相应的部分。1 Right* right = bottom;如果我们要使这段代码正常工作的话

5、,我们需要调整指针指向通过调整,我们可以用right指针访问Bottom对象,这时Bottom对象表现得就如Right对象。但是bottom和right指针指向了不同的内存地址。最后,我们考虑下:1 Top* top = bottom;恩,什么结果也没有,这条语句实际上是有歧义(ambiguous)的,编译器会报错:error:'Top' is an ambiguous base of 'Bottom'。其实这两种带有歧义的可能性可以用如下语句加以区分:1 Top* topL = (Left*) bottom;2 Top* topR = (Right*) bot

6、tom;同样topR和right也将这两个赋值语句执行之后,topL和left指针将指向同一个地址, 指向同一个地址。虚拟继承为了避免上述Top类的多次继承,我们必须虚拟继承类1 class Top2 3 public:4 int a;5 );67 class Left: virtual public Top8 9 public:10 int b;11 );1213 class Right: virtual public Top14 15 public:16 int c;17 );1818 class Bottom : public Left, public Right19 20 public

7、:21 intd;22 );2412述代码将产生如下的类层次图(其实这可能正好是你最开始想要的继承方式对于程序员来说,这种类层次图显得更加简单和清晰,不过对于一个编译器来说,这就复杂得多了。我们再用Bottom的内存布局作为例子考虑,它可能是这样的:BottomLeft:'Top: aLeft: hRight :c这种内存布局的优势在于它的开头部分(Left部分)和Left的布局正好相同,我们可以很轻易地通过一个Left指针访问一个Bottom对象。不过,我们再来考虑考虑Right:1 Right* right = bottom;虚拟继承为了避免上述Top类的多次继承,我们必须虚拟继承

8、类Top。class Toppublic :int a;);class Left: virtual public Top(public :int b;);class Right: virtual public Top(public :int c;);class Bottom : public Left, public Right(public :34566789101213141516171818192021int d;精彩文档22 ); 2412述代码将产生如下的类层次图(其实这可能正好是你最开始想要的继承方式 )。对于程序员来说,这种类层次图显得更加简单和清晰,不过对于一个编译器来说,这就

9、复杂得多了。我们再用 Bottom的内存布局作为例子考虑,它可能是这样的:这种内存布局的优势在于它的开头部分(Left部分)和Left的布局正好相同,我们可以很Right:轻易地通过一个Left指针访问一个Bottom对象。不过,我们再来考虑考虑1 Right* right = bottom;这里我们应该把什么地址赋值给right指针呢?理论上说,通过这个赋值语句,我们可以把这个right指针当作真正指向一个Right对象的指针(现在指向的是Bottom)来使用。但实际上这是不现实的! 一个真正的Right对象内存布局和Bottom对象Right部分是完全不同的,所以其实我们不可能再把这个up

10、casted的bottom对象当作一个真正的right对象 来使用了。而且,我们这种布局的设计不可能还有改进的余地了。这里我们先看看实际上内存是怎么分布的,然后再解释下为什么这么设计。上图有两点值得大家注意。第一点就是类中成员分布顺序是完全不一样的(实际上可以说是正好相反)。第二点,类中增加了 vptr指针,这些是被编译器在编译过程中插入到类中的(在设计类时如果使用了虚继承,虚函数都会产生相关vptr)。同时,在类的构造函数中会对相关指针做初始化,这些也是编译器完成的工作。 Vptr指针指向了一个“virtual table ”。在类中每个虚基类都会存在与之对应的一个vptr指针。为了给大家展

11、示virtual table作用,考虑下如下代码。Bottom* bottom = new Bottom o ;Left* left = bottom;第二条的赋值语句让left指针指向和bottom同样的起始地址(即它指向Bottom对象的“顶部”)。我们来考虑下第三条的赋值语句。1 movl left, %eax # % eax = left2 movl(%eax), % eax# % eax = left .vptr.Left3 movl(%eax), % eax# % eax = virtual base offset4 addlleft, % eax # %eax = left +

12、virtual base offset5 movl(%eax), % eax# % eax = left .a6 movl % eax , p# p = left .a总结下,我们用left指针去索引(找到)virtual table,然后在virtual table 中获取到虚基类的偏移(virtual base offset, vbase),然后在left指针上加上这个偏移量,这样我们就获取到了 Bottom类中Top类的开始地址。 从上图中,我们可以看到对于Left指针,它的virtual base offset是20,如果我们假设Bottom中每个成员都是4字节大小,那么Left指针加

13、 上20字节正好是成员a的地址。我们同样可以用相同的方式访问Bottom中Right部分。1 Bottom* bottom = new Bottom。;2 Right* right = bottom;3 int p = right->a;right指针就会指向在Bottom对象中相应的位置。right T这里对于P的赋值语句最终会被编译成和上述left相同的方式访问a。唯一的不同是就是vptr ,我们访问的vptr现在指向了 virtual table另一个地址,我们得到的virtual base offset也变为12。我们画图总结下:当然,关键点在于我们希望能够让访问一个真正单独的R

14、ight对象也如同访问一个经过upcasted (到Right对象)的Bottom对象一样。这里我们也在Right对象中引入vptrs。OK,现在这样的设计终于让我们可以通过一个Right指针访问Bottom对象了。不过,需要提醒的是以上设计需要承担一个相当大的代价:我们需要引入虚函数表,对象底层也必Leftvptr Left : viable for Left Left.:b: intTop:a: imMabie for Left virtual offset: int of依眈 to top ; Int = 0Rightviable Tar Rightvptr Right: vtaLle

15、for RightRiciht:: i: : iniTop.a : intpoint; to r,JMrtualoffset: Int = SnffseitAtop : im = 0 typeinfo : lypdnfo for Right须扩展以支持一个或多个虚函数指针,原来一个简单的成员访问现在需要通过虚函数表两次间接寻址(编译器优化可以在一定程度上减轻性能损失 )。Down cast ing如我们猜想,将一个指针从一个派生类到一个基类的转换(casting)会涉及到在指针上添加偏移量。可能有朋友猜想,dow ncast ing 一个指针仅仅减去一些偏移量就行了吧。实际上,非虚继 承情况下

16、确实是这样,但是,对于虚继承来说,又不得不引入其它的复杂问题。 这里我们在上面的例子中添加一些继承关系 :1 class Ano therBottom : public Left, public Right2 3 public:4 inte;5 intf;6 );这个继承关系如下图所示那么现在考虑如下代码1 Bottom* bottoml = new Bottom o ;2 AnotherBottom* bottom2 = new AnotherBottom();3 Top* topi = bottoml;4 Top* top2 = bottom2;5 Left* left = static_

17、cast <Left*>(top1);F 面这图展示了 Bottom 和 AnotherBottom的内存布局,同时也展示了各自top指针所指向的位置。topiAno therBottomvpthLeftLeft; :bvptrRjghtRight: cAnotherBQvtorTi;ftop2 ->Top:;atopi 指现在我们来考虑考虑从topi到left的static_cast ,注意这里我们并不清楚对于针指向的对象是Bottom还是AnotherBottom。这里是根本不能编译通过的! 因为根本不能确认topi运行时需要调整的偏移量(对于Bottom是20 ,对于A

18、notherBottom 是24)。所以编译器将会提出错误 :error: cannot convert from base 'Top' to derived type 'Left'via virtual base 'Top,。这里我们需要知道运行时信息,所以我们需要使用dyn amic_cast :1 Left* left = dyn amic_cast <Left*>(top1);不过编译器仍然会报错的error: cannot dyn amic_cast 'top' (of type 'class Top*) t

19、otype 'class Left*' (source type is not polymorphic)。 关键问题在于使用 dynamic_cast(和使用typeid 一样)需要知道指针所指对象的运行时信息。但是,回头看看上面的结构图,我们就会发现topi指针所指的仅仅是一个整数成员a。编译器没有在Bottom类中包含针对top的vptr,它认为这完全没有必要。为了强制编译器在Bottom中包含top的vptr,我们可以在top类里面添加一个虚析构函数。1 class Top2 3 public:4 virtual Top() 5 int a;6 );这就迫使编译器为Top

20、类添加了一个vptr。下面来看看Bottom新的内存布局:Bottomwptr.Ljeft: vtible f or Bottom :intwprtr.Right: vtable for BottomFlight:堆:lnttiattom: : d : iniwptrTap : vt&ble for BottomTop:.a inxvtab Ie forBonam point; To wlnual offset: int =20 offset to top : mt = 0point 5 v.itypeirft:评“inf。kiEsouomvirtual base offset: in

21、t = 12offsei to top : ini = 6type info : typeinfo for Boltcim virtual bue off ret: Irit - 0offset to t op : int = 20typeirfo :iypeinfo for BonomTmpATopO是的,其它派生类(Left、Right)都会添加一个vptr.top,编译器为dynamijcast生成了 一个库 函数调用。1 left = _dynamic_cast (topi, typeinfoJor_Top, typeinfo_for_Left, -1);_dynamic_cast 定

22、义在 libstdc+ (对应的头文件是 cxxabi.h),有了 Top、Left 和 Bottom 的 类型信息,转换得以执行。其中,参数-1代表的是类Left和类Top之间的关系未明。女口果想详细 了解,请参看tin fo.cc的实现。二级指针这里的问题初看摸不着头脑,但是细细想来有些问题还是显而易见的。这里我们考虑一个问题,还 是以上节的Dow ncast ing中的类继承结构图作为例子。2 Bottom* b = new Bottom o ;3 Right* r = b;(在把b指针的值赋值给指针r时,b指针将加上8字节,这样r指针才指向Bottom对象中Right 部分)。因此我们

23、可以把Bottom*类型的值赋值给Right*对象。但是Bottom*和Right*两种类型的 指针之间赋值呢?1 Bottom* bb = &b;2 Right* rr = bb;编译器能通过这两条语句吗?实际上编译器会报错:error: in valid con version from'Bottom*' to ,Right*'为什么?不妨反过来想想,如果能够将bb赋值给rr,如下图所示。所以这里bb和rr两个指针都指向了 b, b和r都指向了 Bottom对象的相应部分。那么现在考虑考虑如果给*rr赋值将会发 生什么。1 *rr = b;注意*rr是Rig

24、ht*类型(一级)的指针,所以这个赋值是有效的!这个就和我们上面给r指针赋值一样(*rr是一级的Right*类型指针,而r同样是一级Right*指针)。所以,编译器将采用相同的方式实现对*什的赋值操作。实际上,我们又要调整b的值,加上8字节,然后赋值给*rr,但是现在*rr其实是指向b的!如下图呃,如果我们通过rr访问Bottom对象,那么按照上图结构我们能够完成对Bottom对象的访问,但是如果是用b来访问Bottom对象呢,所有的对象引用实际上都偏移了8字节一一明显是错误的!总而言之,尽管*a和*b之间能依靠类继承关系相互转化,而*a和*b不能有这种推论。虚基类的构造函数编译器必须要保证所

25、有的虚函数指针要被正确的初始化。特别是要保证类中所有虚基类的构造函数都要被调用,而且还只能调用一次。如果你写代码时自己不显示调用构造函数,同样考虑下上面的编译器会自动插入一段构造函数调用代码。这将会导致一些奇怪的结果,类继承结构图,不过要加入构造函数。1 class Top2 (3 public :4 Top() a = - 1;5 Top( int _a) a = _a;6 int a;7 );89 class Left : public Top10 (11 public :12 Left() b = - 2;13 Left( int _a, int _b) : Top(_a) b = _b;14 intb;15 );1616 class Right: public Top17 18 public :19 Right。 c = 3;20 Right( int _a, int _c) : Top(_a) c = _c;21 int c;22 );2423 class Bottom : public Left, public Right24 25 public :26 Bottom() d = -4 ;27 Bottom( int

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论