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++对象模型之后,才能真正去了解构造函数和析构函数的意义。