深度探索C++对象模型

根据这个图片我们可以看出,对于虚继承体系来说,任何一个对象的构造执行顺序总是从深到浅、从远到近,既最先构造的总是最底层的基类。

      这样看起来似乎并没有什么值得奇怪的地方,但是让我们想想,如果我们把主函数main中的代码改成【Point3d p3d;】呢?相信按下F5后,会输出两句执行结果:【This is Point construct!】和【This is Point3d construct!】,这是因为在构造Point3d对象时,会先执行Point的构造器。看到这里,细心的朋友或许会生出疑问了:不是说在构造PVertex对象时,执行完Point的构造器,还会执行Vertex、Point3d等等类的构造器吗,而在这些类的构造中,不还是要重新执行Point类的构造器吗?如果真是这样,那Point构造器应该被执行很多次啊,那为什么我们只看到一句【This is Point construct!】呢??

      嗯,按道理来说,是应该如此,但这种“讲道理”实在是太浪费效率了:因为对于那些多层继承来说,这种构造方式无疑是一种冗余大灾难!所以,C++的先驱们,就设计出一种黑魔法,让编译器可以不讲道理式的高效率构造对象,而这种黑魔法,我称之为“隐参选构法”。顾名思义,就是在执行一个派生类对象的构造时,会有条件的选择要不要执行基类的构造器。具体做法就是,给继承体系中所有的构造器传入一个隐形参数,根据这个参数的具体数值来确定要不要调用基类的构造器。再具体一点的说,根据书中的描述,这个隐形参数的名字叫做【_most_derived】,是紧跟隐形this指针参数的第二个参数,它有两个值,一个为true,一个为false,当参数为true时,那么它就调用最基类的Point构造器,否则就不调用。

      那么什么时候值才是true呢?答案是当我们声明一个完整的派生类对象时,比如我们例子中的代码【PVertex pv;】,因为pv是一个完整派生类对象,所以编译器在调用它的构造器时,会传入一个true值【1】,所以代码【PVertex pv;】会被编译器扩展成大概这样的形式【PVertex pv(&pv,1);】。那么什么时候传入的值是false呢?答案是在任何一个派生类对象的构造器中,递归调用上一层类的构造器时。比如我们的PVertex类,在编译器给它合成的构造器里面,一定会调用它的继承类Vertex3d类的构造器,而在调用后者时,一定会传入一个false值【0】,以此类推,Vertex3d在调用Vertex和Point3d时,也会传入false值;

http://home.focus.cn/forum/thread/76c8f0cac6eb090fa2a069d59823a5d5.html
http://home.focus.cn/forum/thread/2e97bba8571f43a2d316a3e88203c88a.html
http://home.focus.cn/forum/thread/b5964a5464fbf4956c7f17c9d005fdf0.html
http://home.focus.cn/forum/thread/1f55fb18c6134262046293c5c383ee10.html
http://home.focus.cn/forum/thread/c4fc1a7a014f7f0a8fecabc775283214.html
http://home.focus.cn/forum/thread/80828c42f2d6ba20729f7ba66c8f9ef8.html
http://home.focus.cn/forum/thread/a0462f0053d4290b8f3b1d2363af2454.html
http://home.focus.cn/forum/thread/f61910d264a6e386fe0a4f366bffab76.html
http://home.focus.cn/forum/thread/a3fddca8dd68c69ca1e4b255b62729fb.html
http://home.focus.cn/forum/thread/f04a938b0ee6c4061927eab767762104.html

      说了这么多,让我们来看一下编译器给PVertex类合成的构造器内部代码大概是什么样子的【以下伪代码只是鄙人推测,未必如实!】:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. PVertex* PVertex::PVertex(PVertex *thisbool _most_derived, int x, int y, int z)  
  2. {  
  3.     //如果是声明一个完整类对象,那么默认传入一个true值,所  
  4.     //以在我们的例子中表达式结果为真,Point构造器得以执行。  
  5.     if (_most_derived != false)  
  6.         this->Point::Point(x, y);  
  7.       
  8.     //只要是在派生类的构造器中递归调用上一层类的构造器,传  
  9.     //入的值一定是false,所以Vertex3d构造器中的Point构造器  
  10.     //不会得到执行!以此类推,在Vertex3d中调用Vertex和  
  11.     //Point3d时,Point构造器也不会执行  
  12.     this->Vertex3d::Vertex3d(false, x, y, z);  
  13.       
  14.     //接着我们需要设置一下虚表指针等  
  15.     this->_vptr_PVertex = _vtbl_PVertex;  
  16.     this->vptr_PVertex_Point = _vtbl_PVertex_Point;  
  17.   
  18.     /***************************************** 
  19.     这里可以用来安插一些用户自己写的构造器代码 
  20.     ******************************************/  
  21.   
  22.     //最后返回this指针  
  23.     return this;  
  24. }  

      在现代编译器中,会把每一个construct一分为二,一种针对完整的继承对象,另一种针对子对象【subobject】;完整版无条件调用virtual base construct,设置所有的vptrs,而“subobject”则不调用virtual base construct,也可能不设置vptrs。无疑这是一种提高对象构造效率的方法,毕竟在构造器里面省却了条件分支判断了,代价则是编译后代码的臃肿。

      讲完了constructor,接下来再讲一讲destructor的工作原理,我们就只说一下它的工作步骤好了,如果有读者感兴趣,请自行阅读《深度探索C++对象模型》一书第五章最后一节的内容,大约在235页:

1):destructor函数本身首先被执行。

2):如果该类拥有成员类对象,而这些成员类对象又拥有destructor,那么它们会以声明次序的相反顺序逐个被调用。

3):如果object内带一个vptr,则就在现在被重新设定,指向适当的基类的虚函数表。

4):如果该类有上一层的nonvirtual base class,并且这些基类们拥有destructor,那么就按照它们的声明次序的相反顺序调用。

5):如果有任何的virtual base class拥有destructor,而当前讨论的这个类是最尾端【most derived】的类,那么它们会以其原来的构造顺序相反的顺序被调用。

类似constructor,目前对于destructor的实现策略也是维护两份destructor实体:

1):一个complete object【完全对象】实体,它总是设定好vptrs,并调用virtual base class的destructor。

2):一个base class object实体,除非在destructor调用一个virtual function,否则它绝对不会调用virtual function或设定vptrs。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值