1.《深入理解C++面向对象机制》系列的博文是博主阅读《深度探索C++对象模型》之后的自我总结性质的文章。当然也希望这些文章能够帮助那些想深入了解C++的网友。
2.文章中会有一些被称为“编译器生成的代码”,这些代码并不是编译器真正的生成代码,只是为了方便讨论而写的模拟代码。
3.如果觉得文章对你有帮助而需要转载,也请阁下能够注明出处。
4.如果觉得博文对问题的讨论有误,也可以给博主留言。
我们先来看一个例子。
class CX ; class CBase1 ; class CBase2 ; class CDerived;
其中CBase1和CBase2都是CX的派生类,CBase1和CBase2都是CDerived的基类。当我们有一个CDerived的对象的时候,我们只想要一个CX的subobject。在上一篇文章讨论的多继承下,CDerived对象会存在CBase1的subobject和CBase2的subobject。而这两个subobject都会包含CX的subobject。
这样一个CDerived对象就会有两个CX的subobject,这样和我们的要求不一样(我们只想要一个呀!!!)
所以就有了虚继承。
按照讨论的惯例,我们先提供一个虚继承的例子。
class CX { public: CX(); virtual ~CX(); private: int m_x; public: virtual void Fun(); }; class CBase1 : public virtual CX { public: CBase1(); virtual ~CBase1(); private: int m_y; public: virtual void Fun(); virtual void Fun1(); }; class CBase2 : public virtual CX { public: CBase2(); virtual ~CBase2(); private: int m_z; public: virtual void Fun(); virtual void Fun2(); }; class CDerived : public CBase1,CBase2 { public: CDerived(); ~CDerived(); private: int m_f; public: void Fun(); void Fun1(); void Fun2(); virtual void Fun3(); };
接下来便是virtual table图。
图1.0
这张virtual table图相对于之前,变得更加复杂。对于这张图有一些要说明的(以免引起误会)。
首先这次我将vptr放在类的底部,之前这个指针都是放在类的顶部的。放在底部仅为了讨论的方便。并没有规定编译器一定要将vptr放在类的顶部或者底部。
其次虚继承下的,virtual table又多了一个“offset to CX subobject”,这个槽的序号往往是一个负数,比如这里便是-1;type_info的槽序号便是0;虚函数槽都是正数。
而offset这是为了将this指针移向virtual base subobject的一个偏移量。
根据上一篇文章多继承的讨论,我们将CDerived第一个基类CBase1的virtual table作为主表,第二个基类CBase2的virtual table作为次表。
这里的变化就是又多了一个CX的virtualtable。
最后我们在CDerived的class图中,就会发现只有一个CX的subobject,而我们只在CBase1和CBase2的virtual table中存放CX subobject的位置(通常就是一个offset)。
上一小节,我们展现了虚继承下的virtual table图和它是如何实现虚函数的。那么我们怎么样使CX只构造一次呢?
我的意思是如何抑制CX调用两次构造函数。
最常用的做法,在构造函数中再安插一个bool的参数,_b_most_derived。
现在我们展现一下CDerived的构造函数在编译器下展开成什么样。
CDerived::CDerived(CDerived * this,bool _b_most_derived) { // if (!_b_most_derived) this->CX::CX(); _b_most_derived = false; this->CBase1::CBase1(_b_most_derived); this->CBase2::CBase2(_b_most_derived); //省略 }
上面的代码就是对于这个问题的解决方法。编译器使用_b_most_derived去抑制上层再去调用CX的构造函数。
这里需要说明的是,构造函数的展开远没有上面代码这么简单。这里只为讨论本文的主题,故而省去了许多细节。对于构造函数的详细讨论将在下一篇博文中讨论。
这里我再次声明一次,上面的virtual table不是编译器真的会这么生成。博主只是为方便讨论才做的,当然也参考了《深度探索C++对象模型》。这里只是说明博主所介绍的只是一种编译器实现虚继承的一种方法。
各种继承方式的多态特性也讨论完毕。下面的文章将介绍构造函数和析构函数。这两个函数看似简单,却暗藏了太多东西。只有深入了解了C++对象模型之后,才能真正去了解构造函数和析构函数的意义。