第一章 关于对象
在C语言中,"数据"和"处理数据的操作(函数)"是分开声明的,也就是说,语言本身并没有支持"数据和函数"之间的关联性.我们把这样的程序方法成为程序性,由一组"分布在各个以功能为导向的函数中"的算法所驱动,它们处理的是共同的外部数据.举个样例,假设声明一个struct Point3d,像这样:
typedef struct point3d { float x; float y; float z; } Point3d;
欲打印一个Point3d,可能就得定义一个像这种函数:
void Point3d_print (const Point3d *pd) { printf("(%g, %g, %g)", pd->x, pd->y, pd->z); }
或者,假设要想更有效率一些,就定义一个宏:
#define Point3d_print(pd) printf("(%g, %g, %g)", pd->x, pd->y, pd->z);
也可直接在程序中完毕其操作
在C++中,Point3d有可能用独立的"抽象数据类型(abstract data type)来实现:
class Point3d { public: Point3d(float xval = 0.0, float yval = 0.0, float zval = 0.0) : x(xval), y(yval), z(zval) {} float getX() { return x; } float getY() { return y; } float getZ() { return z; } void setX(float xval) { x = xval; } private: float x; float y; float z; }; inline ostream & operator << (ostream &os, const Point3d &pt) { os << "(" << pt.getX() << ", " << pt.getY() << ", " << pt.getZ() << ")"; }
更进一步来说,无论哪一种形式,它们都能够被參数化,能够是坐标类型的參数化.
template <class type> Point3d(type xval = 0.0, type yval = 0.0, zval = 0.0);
也能够是坐标类型和坐标数目两者都參数化:
template <class type, int dim> Point(type coords[dim]);
非常明显,不仅仅是程序风格上有截然的不同,在程序的思考上也有明显的差异,从软件project的眼光来看"一个ADT或者class hierarchy的数据封装"比"在C程序中程序性使用全局数据"好.,可是这被那些"被要求高速让一个应用程序上马应战,而且运行起来又快又有效率"的程序猿所忽略,毕竟C的吸引力就在于它的精瘦和简易.
加上封装后的布局成本(Layout Costs for Adding Encapsulation)
程序猿看到Point3d转换成C++之后,第一个可能会问的问题就是:加上了封装之后,布局成本添加了多少?答案是class Point3d并没有添加成本,三个data member直接内含在每个class object之中,就像C struct的情况一样,而member functions尽管含在class的声明内,却不出如今object中,每个non-inline
member function仅仅会诞生一个函数实体,至于每个"拥有零个或者一个定义"的inline function则会在其每个使用者(模块)身上产生一个函数实体.Point3d支持封装性质,这一点并未带给它不论什么空间或运行期的不良效应.C++在布局以及存取时间上基本的额外负担是由virtual引起,包含:
virtual function机制 用于支持一个有效率的"运行器绑定"(runtime binding)
virtual base class 用以实现"多次出如今继承体系中的base class,有一个单一而被共享的实体"
此外,另一些多重继承下的额外负担,发生在"一个derived class和其第二或者后继之base class的转换"之间.然而,一般言之,并理由说C++程序一定比C庞大或者迟缓.
1.1 C++对象模式(The C++ Object Model)
在C++中,有两种class data members:static 和 nonstatic,以及三种class member functions:static,nonstatic和virtual,已知以下这个class Point声明:
class Point { public: Point(float xval); virtual ~Point(); float getX() const; static int PointCount(); protected: virtual ostream& print(ostream &os) const; float x; static int point_count; };
这个class Point在机器中会被如何表现呢?也就是说,如何模拟(modeling)出各种data members和function members呢?
1.1.1 简单对象模型 (A Simple Object Model)
第一个模型很easy,它可能是为了尽量减少C++编译器的设计复杂度而开发出来的,缺点则是空间和运行期的效率低下.在这个简单模型中,一个object是一系列的slot(槽),每个slot指向一个member.Members依照声明次序,各自被指定一个slot.每个data member或function member都有自己的一个slot.
在这个简单模型中,members本身并不被放在object之中,仅仅有"指向member的指针"才被放在object内,这么做能够避免"members有不同的类型,因而须要不同的存储空间"所导致的问题.Object中的members是以slot的索引值来寻址,本例中x的索引值是6,point_count的索引值为7.一个class object的大小非常easy计算出来:"指针大小,乘以class中声明的members数目".
尽管这个模型并没有被应用于实际产品上,只是关于索引或slot数目的观念,倒是被应用到C++的"指向成员的指针"(point-to-member)观念之中.
1.1.2 表格驱动对象类型 (A Table-driven Object Model)
为了对全部classes的全部objects都有一致的表达方式,还有一种对象模型是把全部与members相关的信息抽出来,放在data member table和一个member function table之中,class object本身则内含这两个表格的指针,Member function table是一系列的slots,每个slot指出一个member function; Data member table则直接含有data本身.
尽管这个模型也没有实际应用于真正的C++编译器上,但member function table这个观念却称为支持virtual functions的一个有效方案.
1.1.3 C++对象模型 (The C++ Object Model)
Stroustrup当初设计(当前仍占有优势)的C++对象模型是从简单对象模型派生而来的,并对内存空间和存取时间做了优化.在此模型中,Nonstatic data members被配置于每个class object之内,static data members则被存放在全部的class object之外,Static和nonstatic function members也被放在全部的class object之外,Virtual functions则以两个步骤支持之:
1.每个class产生出一堆指向virtual functions的指针,放在表格之中,这个表格被称为virtual table(vtbl)
2.每个class object被加入了一个指针,指向相关的virtual table,通常这个指针被称为vptr.vptr的设定和重置都由每个class的constructor,destructor和copy assignment运算符自己主动完毕,每个class所关联的type_info object也经由virtual table被指出来,一般是放在表格的第一个slot处.
C++对象模型的主要长处在于它空间和存取时间的效率;主要缺点是。假设应用程序代码本身并未改变,但所用的class object的nonstatic data members有所改动(可能是添加、移除或改动),那么那些应用程序代码相同得又一次编译。
关于这点,前面的的表格驱动模型就提供了较大的弹性,由于它多提供了一层间接性。只是它也因此付出空间和运行效率双方面的代价。