第一章 - 关于对象
什么是C++对象模型
有两个概念可以解释C++对象模型:
- 语言中直接支持面向对象程序设计的部分
- 对于各种支持的底层实现机制
C++对象模型的第一个概念是一种“不变量"。例如,C++ class的完整virtual functions在编译时期就固定下来了,程序员没有办法在执行期动态增加或取代其中某一个。这使得虚拟调用操作得以有快速的派送(dispatch)结果,付出的成本则是执行期的弹性。
目前所有编译器对于virtual function的实现法都是使用各个class专属的virtual table,大小固定,并且在程序执行前就构造好了。
C++ 第一套编译器cfront
原文名词
DETAILS
OMG和Microsoft分别提出CORBA(Common Obect Request Breaker Architecture) 和 COM(Component Object Model)标准, CORBA模型主要应用于UNIX操作系统平台上,而COM则主要应用于Microsof tWindows操作系统平台上。
SOM(System Object Model)
概述
在c语言中,“数据"和“处理数据的操作(函数)”是分开来声明的,即语言本身并没有支持“数据和函数"之间的关联性。我们把这种程序方法称为程序性的(procedural)。
struct结构体里一般只定义数据类型,或构造函数? TODO
操作符重载一般也是inline
加上封装后的布局成本(Layout Costs for Adding Encapsulation)
data members直接内含在每一个class object之中,就像C struct的情况一样,而member functions虽然含在class的声明之内,却不出现在object之中,每一个non-inline member function只会诞生一个函数实体,至于每一个"拥有零个或一个定义"的inline function 则会在其每一个使用者(模块)身上产生一个函数实体。
C++在布局以及存取时间上主要的额外负担是由virtual引起,包括:
- virtual function机制用以支持一个有效率的“执行期绑定"(runtime binding)。
- virtual base class 用以实现“多次出现在继承体系中的 base class,有一个单一而被共享的实体。
此外,还有一些多重继承下的额外负担,发生在"一个derived class和其第二或后继之baseclass的转换"之间。然而,一般言之,并没有什么天生的理由说C++程序一定比其c兄弟庞大或迟缓。
C++ 对象模式
在C++中,有两种class data members:static和nonstatic,以及三种class member functions:static、nonstatic和virtual。已知下面这个class Point声明:
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
简单对象模型(A Simple Object Model)
第一个模型十分简单,它可能是为了尽量减低C++编译器的设计复杂度而开发出来的,赔上的则是空间和执行期的效率。在这个简单模型中,一个object是一系列的slots,每一个slot指向一个members。Members按其声明顺序,各被指定一个slot。每一个data member或function member都有自己的一个slot。
在这个简单模型中,members本身并不放在object之中,只有“指向member的指针”才放在object内。这么做可以避免“members有不同的类型,因而需要不同的存储空间”所招致的问题。
虽然这个模型并没有被应用于实际产品上,不过关于索引或slot数目的观念,倒是被应用到C++的“指向成员的指针"(pointer-to-member)观念之中。
表格驱动对象模型(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的一个有效方案。
C++对象模型(The C++ Object Model)
Stroustrup当初设计(当前亦仍占有优势)的C++对象模型是从简单对象模型派生而来的,并对内存空间和存取时间做了优化。在此模型中,Non static data members被配置于每一个class obJect之内,static data members则被存放在所有的class object之外,Static 和 nonstatic function members也被放在所有的class object之外。
Virtual functions则以两个步骤支持之:
每一个class产生出一堆指向 virtual functions 的指针,放在表格之中.这个表格被称为 virtual table(vtbl)。
每一个 class object 被安插了一个指针,指向相关的virtual table。通常这个指针被称为vptr。vptr的设定(setting)和重置(resetting)都由每一个class的constructor、destructor 和 copy assignment 运算符自动完成。每一个class所关联的 type_info object (用以支持 runtime type identification,RTTI) 也经由virtual table被指出来,通常是放在表格的第一个slot处。
vtbl属于类,vptr属于类对象。
这个模型的主要优点在于它的空间和存取时间的效率;主要缺点则是,如果应用程序代码本身未曾改变,但所用到的class objects的nonstatic data members有所修改(可能是增加、移除或更改),那么那些应用程序代码同样得重新编译,关于这点,前述的双表格模型就提供了较大的弹性,因为它多提供了一层间接性,不过它也因此付出空间和执行效率两方面的代价就是了。
sizeof(ClassName)= Non static data members 的大小 + vptr的大小(如果有虚函数的话) + 字节对齐
加上继承
class istream : virtual public ios {...};
class ostream : virtual public ios {...};
class iostream : public istream,ostream {...};
2
3
在虚拟继承的情况下,base class不管在继承串链中被派生(derived)多少次,永远只会存在一个实体(称为sub object)。例如,iostream 之中就只有virtual ios base class的一个实体。
关键词所带来的差异(A Keyword Distinction)
如果不是为了努力维护与C之间的兼容性,C++远可以比现在更简单。
什么时候一个人应该使用struct取代class? ————> 当它让一个人感觉比较好的时候。
//继承方式
struct C_point {...};
class Point : public C_point {...};
//组合方式
class Point {
public:
operator C_point() { return _c_point; }
...
private:
C_point _c_point;
}
2
3
4
5
6
7
8
9
10
11
12
组合而非继承,才是把C和C++结合在一起的唯一可行方法。
对象的差异(An Object Distinction)
C++程序设计模型直接支持三种programming paradigms(程序设计典范):
paradigm 含义
一种环境设计和方法论的模型或范例:系统和软件以此模型来开发和运行。一个现役的典范可能会有数个开发中的替代典范。以下是一些大家比较熟悉的典范:函数化程序设计、逻辑程序设计、语意数据模型、几何计算、数值计算、面向对象设计、原型设计、自然语言。
- 程序模型(procedural model),就像C一样,C++当然也支持它。字符串的处理就是一个例子。
- 抽象数据类型模型(abstract datatype model,ADT)。该模型所谓的“抽象”是和一组表达式(public接口)一起提供,而其运算定义仍然隐而未明,例如string class。
string addr = "nanjing";
string city;
// string::operator=()
city = addr;
// string::operator==()
if( city == addr)
2
3
4
5
6
7
- 面向对象模型(object-oriented model)。在此模型中有一些彼此相关的类型,通过一个抽象的base class(用以提供共通接口)被封装起来。
void check_in(Library_materials *pmat)
{
if( pmat->late() )
pmat->fine()
pmat—>check_in();
if( Lender *plend=pmat->reserved() )
pmat—>notify(plend);
...
}
2
3
4
5
6
7
8
9
10
纯粹一种paradigm写程序,有助于整体行为的良好稳固。然而如果混合了不同的paradigms,就可能会带来让人惊吓的后果,特别是在没有谨慎处理的情况下。最常见的疏忽发生在当你以一个base class的具体实体如:
Library_materials thing1
来完成某种多态(polymorphism)局面时:
Library_materials thing1;
Book book;
thing1 = book;
thing1.check_in();//调用Library_materials::check_in()
//应该多使用
Library_materials &thing2 = book;
thing2.check_in();//调用Book::check_in()
2
3
4
5
6
7
8
9
10
11
虽然你可以直接或间接处理继承体系中的一个base class object,但只有通过pointer或reference的间接处理,才支持OO程序设计所需的多态性质。
C++以下列方法支持多态:
- 经由一组隐含的转化操作,例如把一个 derived class 指针转化为一个指向其public base type的指针:
shape *ps= new circle();
- 经由virtual function机制:
ps->rotate();
- 经由 dynamic_cast 和 typeid 运算符、
if(circle *pc = dynamic_cast<circie*>(ps) ) ...
多态的主要用途是经由一个共同的接口来影响类型的封装,这个接口通常被定义在一个抽象的base class中。当派生类型有所增加、修改、或删减时,我们的程序代码不需改变,只需要派生类实现基类的接口即可。
需要多少内存才能够表现一个class object?
一般而言要有:
- 其nonstatic data members的总和大小:
- 加上任何由于alignment的需求而填补上去的空间
- 加上为了支持 virtual 而由内部产生的任何额外负担(overhead)
指针的类型(The Type of a Pointer)
一个指针,不管它指向哪一种数据类型,指针本身所需的大小是固定的。
class ZooAnimal{
public:
ZooAnimal();
virtual ~ZooAnimal();
// ...
virtual void rotate();
protected:
int loc;
String name;
}
2
3
4
5
6
7
8
9
10
加上多态之后(Adding Polymorphism)
class Bear : public ZooAnimal{
public:
Bear();
~Bear();
// ...
void rotate();
virtual void dance();
protected:
enum Dances { ... }
Dances dances_known;
int cell_block;
}
2
3
4
5
6
7
8
9
10
11
12
13
Bear b;
ZooAnimal *pz = &b;
Bear *pb = &b;
2
3
除了 ZooAnimal subobject中出现的members,你不能够使用来直接处理Bear的任何members。唯一例外是通过virtual机制:
//不合法:cell_block不是ZooAnimal的一个member,
//虽然我们知道 pz 当前指向一个 Bear object
pz->cell_block,
//ok:经过一个明白的downcast操作就没有问题!
((Bear*)pz)->cell_block;
//下面这样更好,但它是一个run-time operation
if( Bear* pb2 = dynamic_cast<Bear*>(pz) )
pb2->cell_block
//ok:因为cell_block是Bear的一个member.
pb->cell_block;
2
3
4
5
6
7
8
9
10
总结
重要:
当一个base class object被直接初始化为(或是被指定为)一个 derived class object时,derived object 就会被**切割(sliced)**以塞人较小的base type内存中, 多态不再呈现。
多态是一种威力强大的设计机制,允许你继一个抽象的public接口之后,封装相关的类型。需要付出的代价就是额外的间接性一不论是在“内存的获得"或是在“类型的决断"上。C++ 通过class的pointers和references来支持多态,这种程序设计风格就称为“面向对象"。
C++也支持具体的ADT程序风格,如今被称为object-based(OB)。例如String class,一种非多态的数据类型,String class可以展示封装的非多态形式,它提供一个public接口和一个private实现品,包括数据和算法,但是不支持类型的扩充。一个OB设计可能比一个对等的OO设计速度更快而且空间更紧凑。速度快是因为所有的函数引发操作都在编译时期解析完成,对象建构起来时不需要设置 virtual 机制:空间紧凑则是因为每一个class object不需要负担传统上为了支持virtual机制而需要的额外负荷,不过,OB设计比较没有弹性。
在弹性(OO)和效率(OB)之间常常存在着取与舍。一个人在能够有效选择其一之前,必须先清楚了解两者的行为和应用领域的需求。