1.0
C++的编程范式一共有三种,分为程序模型(procedural model)、抽象数据类型模型(abstract data type model, ADT)和面向对象模型(object-oriented model)。这三种范式不止在程序风格上有显著的不同,在程序的思考上也有明显的差异。
在C++中加上封装后的布局成本:与C struct相比并没有增加成本。在C++中,布局及存取时间上的主要额外负担来自virtual引起的,包括:
- virtual function机制 用以支持一个有效率的“执行期绑定”
- virtual base class 用以实现“多次出现在继承体系中的base class,有一个单一而被共享的实例”
1.1 C++对象模型
在C++中,有两种class data member: static 和 nonstatic, 以及三种class member function: static、nonstaic 和 virtual。
在Stroustrup当初设计的模型中,Nonstatic data member 被配置于每一个class object之内,static data member 则被存放在个别class object 之外。static 和 nonstatic function member 也被放在个别的class object 之外。Virtual function 则以两个步骤支持之:
1. 每个 class 产生一堆指向 virtual function 的指针,放在表格中。这个表格被称为 virtual table(vtbl)。
2. 每个class object被安插一个指针,指向相关的 virtual table。通常这个指针被称为 vptr。vptr 的设定和重置都由每一个 class 的 constructor、destructor 和 copy assignment运算符自动完成(在第五章详细讨论)。每一个 class 所关联的 type_info object 也经由 virtual table 被指出,通常放在表格的第一个slot。
继承模型
C++最初的继承模型:base class subobject 的 data member 被直接放置于 derived class object 中。这提供了对 base class members 最紧凑而且最有效的存取。但是,base class member 的任何改变,使得所有用到“此base class 或者其 derived class 之 object”者都必须重新编译。
对于 virtual base object,需要一些间接的 base class 表现方法(我们在第三章详细讨论)。
对象模型如何影响程序
举个栗子,看下面函数,其中class X定义了一个 copy constructor、一个virtual destructor 和一个 virtual function foo:
X foobar()
{
X xx;
X *px = new X;
//foo 是一个 virtual function
xx.foo();
px->foo();
delete px;
return xx;
}
这个函数有可能被转化为:
//可能的内部转换结果
//虚拟C++代码
void foobar(X &_result)
{
//构造 _result
// _result 用来取代 local xx...
_result.X::X();
//扩展X *px = new X;
px = _new( sizeof(X) );
if( px != 0 )
px->X::X();
//扩展 xx.foo() 但不使用virtual
foo( &_result );
//使用 virtual 机制扩展 px->foo();
( *px->vtbl[2] )( px );
//扩展 delete px
( *px->vtbl[1] )( px );
_delete( px );
//无须使用 named return statement
//无须摧毁 local object xx
return;
}
对象的差异
C++以下列方法支持多态:
- 经由一组隐式的转化操作。例如把一个 derived class 指针转化为一个指向其 public base type 的指针:
shape *ps = new circle();
- 经由 virtual function 机制
经由 dynamic_cast 和 typeid 运算符
需要多少内存才能表示一个 class 呢?一般而言需要:
其 nonstatic data members 的总和大小
- 加上任何由于 alignment 的需求而填补上去的空间
- 加上为了支持 virtual 而由内部产生的任何额外负担
指针的类型
指向不同类型的指针,它们之间有什么区别?
以内存需求角度来说,并没有什么不同,都是要有足够的内存来放置一个机器地址(通常是一个word)。
区别在于,涵盖的地址大小不同。例如有:
int a; //假设a的地址为 1000
double b; //假设b的地址为 2000
int *pInt = &a; //pInt:1000
double *pDouble = &b; //pDouble:2000
++pInt; //pInt: 1004
++pDouble; //pDouble: 2008
可以看到,pInt的涵盖的内容大小是一个sizeof(int) = 4,pDouble涵盖的大小是一个sizeof(double)。