根据这个图片我们可以看出,对于虚继承体系来说,任何一个对象的构造执行顺序总是从深到浅、从远到近,既最先构造的总是最底层的基类。
这样看起来似乎并没有什么值得奇怪的地方,但是让我们想想,如果我们把主函数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类合成的构造器内部代码大概是什么样子的【以下伪代码只是鄙人推测,未必如实!】:
在现代编译器中,会把每一个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。