Object Lessons

在C语言中,数据和函数是分开声明的,语言本身并不支持数据和函数之间的关联性

例如

1
2
3
4
5
6
7
8
9
typedef struct point3d {
float x;
float y;
flaot z;
} Point3d;

void Point3d_print {
//print x,y,z
}

而在C++中,则提供了抽象数据模型给予支持

1
2
3
4
5
class Point3d {
public:
Point3d(float x...) : _x(x),...{}
void print() {//print x,y,z}
}

然而,这时候就会有人问,加上了封装之后,布局的成本增加了多少?

答案是并没有增加成本

class Point3d支持封装性质并没有给它带来任何空间和执行期的不良后果

不过你即将看到

C++的布局以及存储时间上的主要额外负担都是由virtual引起的

包括

  • virtual function
  • virtual base class

The C++ Object Model

C++中,有两种class data members:static, nostatic

有三种class member functions:static,nostatic,virtual

思考如下代码在机器中的表现形式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Point {
public:
Point(float xval);
virtual ~Point();

float x() const;
static int PointCount();

protected:
virtual ostream&
print(ostream &os) const;

float _x
static int _point_count;
};

A Simple Object Model

在这个模型中

一个object就是一系列的slots(指针),每一个slot指向一个members

member本身并不存放在object中,只有指向member的指针才会存放在object中

那么 size(obejct) = size(pointer) * count(member)

A Table-driven Object Model

这个模型则把上面的模型进行了改进

他把member分成data和function,然后分别放在data memeber table和member function table中

object则存放指向这两个table的指针,data member table则直接持有data,而member function table则存放着一系列的slots,每一个slot指出一个member function

虽然这个模型没有实际应用到C++编译器中

但是member function table这种观念成为支持virtual function的一个有效方案

The C++ Object Model

在此模型中

non-static data member被配置到每一个object内,static data members则被存放在object之外

static 和 non-static function member也被存放在object之外

virtual functions则通过以下两个步骤来支持

  • 1.每一个class产生一堆指向virtual的指针,放在表格中(vtbl)
    object

  • 2.每一个object被安插一个指针(vptr),指向相关的vtbl,vtpr的设定和重置都由每一个class的constructor, destructor和copy assignment运算符自动完成,每一个class所关联的type_info object也经由vtbl被指出来,通常被放在表格的第一个solt

加上继承

在虚拟继承的情况下,base class不管在继承中被派生多少次,永远只存在一个实例

那么base table应该是怎么样的呢?

首先根据simple object model,我们可以在class object中放置slot指向base class

另外一种机制是,每一个class object都会被安放一个base table pointer

然后继承多个base class只需要改变base table而无需改变class object

但是无论哪一种机制,间接性的级数都会因为继承的深度而增加

例如以下这种继承链

Library_materials—->Book—->Rental_Book

Rental_Book需要两次间接存储才能取到Library_materialsmembers

对象的差异

只有通过Pointer和Reference的间接处理,才能支持OO程序设计中所需的多态性质

在C++中,多态只存于一个个的public class体系中,并以如下的方式支持多态

  • 隐式转换,例如dervied class指针转换成base class指针

  • virtual function机制

  • 经由dynamic_cast和typeid运算符

然后,可以试着思考,需要多少内存才能表现一个class object?

  • nonstatic data members的总大小

  • 加上由alignment的需求而填补上去的空间(alignment就是将数值调整到某数的倍数,32位机器alignment为4 bytes,可以使bus“运输量”达到最高效率

  • 为了支持virtual而产生的额外负担(vptr)

指针的类型

一个指针,不管指向的类型是什么,指针本身所需的内存大小是固定的

但是一个指向整数的指针和一个指向template Array的指针有什么不同呢?

首先,肯定的是,指针的内存需求是一样的

其次,它们之间的差异就在于其所寻址出来的object类型不同

换句话说,就是“指针类型”会教导编译器如何解释某个特定地址中的内存内容和大小

  • 指向地址1000的整数指针,在32位机器上,将涵盖地址空间1000-1003

假设类ZooAnimal

1
2
3
4
5
6
7
8
9
class ZooAnimal {
public:
zooAnimal();
virtual ~ZooAnimal();
virtual void rotate();
protected:
int loc;
string name;
};

  • 一个指向ZooAnimal的指针将跨越地址空间1000-1015(int-4 + string(int+char)-8 + vptr-4)

那么一个指向1000的void *的指针将涵盖怎么样的内存空间呢?

我们并不知道,这也就是为什么void指针能够持有地址却无法操作其指向的object的缘故

加上多态之后

1
2
3
4
class Bear : public ZooAnimal {...};
Bear b;
ZooAnimal za = b;
za.rotate() //ZooAnimal::rotate()

Q1:为什么za的vptr不指向Bear的vtbl?

Q2:为什么rotate调用的是ZooAnimal::rotate()?

A1:如果某个object含有一个或者一个以上的vptrs,那些vptrs不会被用于初始化的source object所改变

A2:我们知道,pointer或者reference之所以支持多态,是因为它们可以改变所指向内存的大小和内容解释方式

而直接进行dervied class object对base class object的赋值操作,因为object所占用的内存大小不一样

因此会对dervied class object进行对象切割以塞入更小的base class object中

于是乎dervied type的成分就会被完全切除,多态也无从表现