Chapter 3. The Semantics of Data : Data 语义学
示例代码:
class X {};
class Y : public virtual X {};
class Z : public virtual X {};
class A : public Y, public Z {};
一个类对象的大小受三个因素的影响
i. 语言本身所造成的额外负担(overhead), 当语言要支持virtual base class 时,就会导致一些额外的负担.
ii. 编译器对特殊情况所提供的优化处理, 如virtual base class class X subobject 的1bytes大小会出现在子类Y, Z的身上.
如: sizeof(Y) = sizeof(Z) = 4(8) // 这里的4(8)和编译器相关
有时候有的编译器会用empty virtual base class 技术来优化, VC就是采用这一技术的, 这样virtual base class 就不用占用大小了.
iii. Alignment 的限制, Y和Z的大小本来大小都是4, 加上virtual base subobject的1bytes的大小共5个字节, 但实际上去是8bytes,这里就是受到字节对齐的影响.
C++对象模型对数据的存放结构是:
i. 把nonstatic data members直接的放在class object之中, 对继承(不管是virtual 或 nonvirtual base class )而来的nonstatic data members也是这一样的.
ii. 没有强制定义其间的排列顺序
iii. 对static data members, 则被放置在一个global data segment中, 不会影响单个类的大小, 并且只保存一份实体. (template有所不同)
3.1 Data Member的绑定(The Binding of Data Member)
示例代码:
1 // A third party foo.h header file
2 // pulled in from somewhere
3 extern float x;
4
5 // the programmer's Point3d.h file
6 class Point3d
7 {
8 public:
9 Point3d( float, float, float );
10
11 // question: which x is returned and set?
12 float X() const { return x; }
13
14 void X( float new_x ) const { x = new_x; }
15
16 //
17
18 private:
19 float x, y, z;
20 };
21
22
在早期的编译器中会出错, 不过在 C++2.0后就不会了, 在C++2.0后, 采用的是"rewriting rule" == "member scope rsolution rule" 规则来处理它.
以前的编译器中, float X() const { return x; }, 它不知道要返回哪一个x, 这里它会返回全局的 extern float x, 所以是不正确的. 后来的编译器是会在整个class的声明都出现了后才会分析member functions, 所以它不会现错.
对于下面的例子还是会出错, 因为对于member functions signatures的分析不会到类完成以后, 而是第一次出现的时候就会分析的. 如下面的:
所以最好始终的把"nested type declare" 放在类的起始处. (这在STL中好像最明显, 都是先声明的)
3.2 Data Member的布局 (Data Member Layout)
示例代码:
1 class Point3d {
2 public:
3
4 //
5
6 private:
7 float x;
8 static List<Point3d*> *freeList;
9 float y;
10 static const int chunkSize = 250;
11 float z;
12 };
13
Data Member的布局按如下的规则:
i. Nonstatic data member 在class object中的排列顺序和被声明的顺序是一样的, 任何中间介入的static data member都不会被放进对象的布局中.
ii. 要求在同一access section中"较晚出现的members在class object中有较高的地址"这一条件就可以.
iii. 编译器可能会合成一些内部使用的data members, 以支持整个对象模型, 如vptr指针. 对于它的具体位置, C++ Standard 没有规定, 由编译器产商自己决定. 不过传统上一般是放在所有声明的members的最后, 也有把vptr放在所有class object的最前端的.
3.3 Data Member的存取
示例:
Point3d origin, *pt = &origin;
origin.x = 0.0;
pt->x = 0.0
1. Static Data Members的存取
每一个static data member只有一个实体,存在于程序的data segment中。每次程序取用这个static data member的时候,就会被转化为对该实体的唯一的extern实体的直接参考操作. 用指针存取一个数和用对象去存取一个数是一样的。
2. Nostatic Data Members的存取
Nostatic data member 直接存放在每一个class object之中,除非经由明确的或暗喻的class object,否则没有办法直接的存取它们。
例如:
Point3d Point3d::translate( const Point3d &pt )
{
x += pt.x;
y += pt.y;
z += pt.z;
}
实际经过转换后为:
Point3d Point3d::translate( Point3d *const this, const Point3d &pt )
{
this->x += pt.x;
this->y += pt.y;
this->z += pt.z;
}
对nostatic data member的访问是这样的:
origin._y = 0.0;
实际转换操作是:
&origin + (&Point3d::_y - 1 );
注意: 这里的-1操作。指向data member的指针,其offset值总是被加上1, 这样可以使编译系统区分出:
i. 一个用以指出class的第一个member的data member的指针.
ii. 一个没有指出任何member的data member的指针.
如果是virtual 继承的话,就可以不一样了,可能要多加层的访问层; 也可能要到运行时才能决定,由编译器所决定.
3.4 “继承”与Data Member
示例数据:
1 // supporting abstract data types
2 class Point2d
3 {
4 public:
5 // constructor(s)
6 // operations
7 // access functions
8 private:
9 float x, y;
10 };
11
12 ///
13 class Point3d
14 {
15 public:
16 // constructor(s)
17 // operations
18 // access functions
19 private:
20 float x, y, z;
21 };
22
C++的继承模型:
在C++的继承模型中, 一个derived class object 所表现出来的东西,是其自己的member加上其base class(es) member的总和。对于数据成员出现的顺序在C++ Standard 中没有规定。从下面几个方面来讨论数据继承:
i. 单一继承且不含有virtual functions
ii. 单一继承并含有virtual functions
iii. 多重继承
iV. 虚拟继承
1. 只要继承不要多态(Inheritance Without Polymoophism)
继承一般不会增加空间或存取时间。但继承有时会有这样两种情况出现:
i. 经验不足的人有时可能会重复的设计一些相同的函数.
ii. 把一个类分解为多层,有可能会为了表现class的体系抽象化,使所需要的空间膨胀。
因为C++语言要保证: 出现在derived class 中的base class subobject 有其完整原样性。
2. 加上多态(Adding Polymorphism)
如:
1 class Point2d
2 {
3 public:
4 Point2d( float x = 0.0, float y = 0.0 )
5 : _x( x ), _y( y ) {};
6
7 // access functions for x & y same as above
8 // invariant across type: not made virtual
9
10 // add placeholders for z ?do nothing
11 virtual float z(){ return 0.0 };
12 virtual void z( float ) {}
13
14 // turn type explicit operations virtual
15 virtual void operator+=( const Point2d& rhs )
16 {
17 _x += rhs.x(); _y += rhs.y();
18 }
19
20 // more members
21
22 protected:
23 float _x, _y;
24 };
25
26
//
要支持多态,Point2d数据成员要做如下的工作:
i. 导入一个和Point2d有关的virtual table(vtbl), 存放它声明的每一个virtual function的地址
ii. 在每个class object中导入一个vptr, 提供执行期的链接,使每个object都能找到相应的virtual table.
iii. 加强construtor, 使它能够为vptr设定初值,让它指向class所对应的virtual table.
iV. 加强destructor, 使它能够抹消"指向class的相关"virtual table" 的vptr.
Figure 3.3. Data Layout: Single Inheritance with Virtual Inheritance
3. 多重继承(Multiple Inheritance)
4. 虚拟继承(Virtual Inheritance)
Class 中如果含一个或多个virtual base class subobjects, 它将被分为两个部分: 一个不变的局部和一个共享的局部.
i. 不变的局部中的数据,不管后继如何衍化,总有固定的offset, 所这一部分的数据可以直接的被存取。
ii. 共享的局部,所表现的就是virtual base class subobject, 这一部分的数据会因为每次派生的操作而有变化, 所以它们只能间接的存取。
3.5 对象成员的效率(Object Member Efficiency)
3.6 指向数据成员的指针(Point to Data Members)