首先看一下C++的对象主要包含那些部分。对象主要包含Data和Methods。其中Data分为Static和NonStatic两类;Methods分为Static、NonStatic和Virtual三类。Stroustrup(牛人啊)是这样设计的:NonStatic Data放在每一个对象中;Static Data、Static Methods和NonStatic Methods放在对象之外,只有一份。而对于Virtual Methods则稍微麻烦些。第一,为每个类对象构造一张表,称为VTable,也就是个(void*)的数组,里面用来放地址。第二,为每个对象增加一个指向VTable的指针,称为VPTR。第三,这个VPTR的设定和改变由Constructor、Destructor和Copy Assignment来操作;第四,VTable的第一项一定是关于这个类的RTTI信息。
这样,一个简单的没有继承层次的对象内存布局类似于: [_data(4B)|vptr(4B)] + s_a(4B) + Foo(4B) + Instance(4B) = 20B。
{
private:
int _data;
static A* s_a;
public:
void Foo();
static void Instance();
} ;
class A : virtual Base { ...... };
class B : virtual Base { ...... };
class D : A, B { ...... };
为了处理继承,我们为一个派生类引入一个基类列表,用来枚举基类对象的内存信息。在派生类中增加一个指向这个基类列表的指针,称为BPTR。这样就可以通过BPTR来找到基类的对象信息。上述层次的内存变成了这个样子:
class Base{ ...... };
class A { _bptr; ......; _vptr; };
class B { _bptr; ......; _vptr; };
class D { _bptr; ......; _vptr; }
而这个D::_bptr则指向这样一个基类列表: [A的对象地址|B的对象地址]。
在这一章中,书中花了几篇的地方来讲解C++中的Struct的存在及原因。归纳如下:最主要的是向C兼容;但是引入Struct后,却产生了大量的问题。但是C++编译器都遵循兼容的准则把这些问题解决了,从而增加了C++的语法多样性。我个人认为,如果采用OO Paradigm就不要使用struct。有的类库,比如MFC都认为如果要表现一种简单值集合的观念,就用Struct。我不反对,你可以根据自己的习惯采纳。因为现代的编译器都会给出UI提示,你能在复用一个类型的时候知道它是Class还是Struct(无论怎样编译器都兼容这个问题)。 其实在DotNET CLR中的值类型和引用类型的分类倒是能够很好的解释C++中的Struct和Class在语义上的区别。