首先,让我们来看一下C++类对象中,各种成员是如何布局的,即C++对象模型。本篇内容也是今后讨论C++底层经常要用到的,因此在进一步深入研究C++的其他主题之前,了解一下本文的内容是非常必要的。
C++对象模型,从简单对象模型到表格驱动对象模型,几经演变,最终形成了C++标准中的对象模型。
1、C++对象模型
众所周知,在C++类中,有两种数据成员:static和nonstatic,三种成员函数:static、nonstatict 和virtual。
在C++对象模型中,nonstatic数据成员被配置于每一个类对象实体内;
static数据成员则被存放在所有的类对象实体之外,即程序的data segment之中;
static 和nonstatic成员函数也被放在所有的类对象之外;
virtual function则以两个步骤实现:
1) 每一个类产生出一堆指向virtual functions的指针,放在一个表格中,这个表格被称为virtual table(vtbl)。
2) 每一个类对象被添加了一个指针,指向相关的virtual table,通常这个指针被称为vptr。Vptr的设定和重置都由每一个类的构造、析构函数和复制运算符自动完成。每一个类用以支持runtime type identification(RTTI)的type_info object也由virtual table指出,通常放在表格的第一项。
以Point类对象为例,整个C++对象模型如下图所示:
这个模型的优点在于它的空间和存取时间的效率;主要缺点则是如果应用程序代码本身未改变,但所用到的类对象的nonstatic数据成员有所修改,那么应用程序的代码同样得重新编译。
2、加上继承之后呢?
C++的继承机制包括:单一继承、多重继承以及虚拟继承,其中在虚拟继承下,基类不管在继承链中被派生多少次,永远只会存在一个实体。
C++采用的继承模型并不运用什么间接性,直接将基类内容放置在派生类对象中。(在派生类对象中的基类部分被称为subobject。)因此,提供了对基类成员最紧凑而且最有效率的途径。缺点则是对于基类的任何改变,派生类都必须重新编译。
3、复杂的虚拟继承
当引入虚拟继承之后,则需要一些间接的基类表现方法。
MetaWare和其它编译器到今天仍然在使用cfront(第一个C++编译器)的原始实现模型,即在派生类中安插一些指针,每个指针指向一个虚拟继承类。
下面让我们来看一下cfront模型是怎么实现的,在这里,我们提供了四个类:
class Point2d { };
class Vertex : public virtual Point2d { };
class Point3d : public virtual Point2d { };
class Vertex3d : public Vertex, public Point3d { };
这四个类的对象模型如下图所示:
Microsoft编译器提供的方法是在派生类对象中导入了一个virtual base class table以便维护每一个virtual base class的地址。
而Bjarne推荐的方法是在virtual function table中保存虚拟基类的offset(注意,这里不是地址),将virtual base class offset 和virtual function entries混杂在一起。
这种base class offset的实现模型如下图所示:
由于对虚拟基类的支持会带来额外的负担(将在以后的文章中详细阐述)以及高度的复杂性,每一种实现模型多少有点不同,而且还会随着时间而不断进化。
对于一个virtual base class而言,最有效的一种运用形式就是:一个抽象的virtual base class,没有任何数据成员。