您的位置:首页 > 移动开发 > Objective-C

《inside the c++ object model》读书笔记 之七 站在对象模型的尖端

2012-10-30 12:26 621 查看
...这一章主要讨论三个著名的C++语言扩充性质,它们分别是:template,exception和runtime type identification.

7.1 template:
有关template的三个主要讨论方向:
1)template的声明,基本上来说就是当你声明一个template class,template class member function等等时,会发生什么事.
2)如何具现出class object以及inline nonmember,以及member template functions,这些是"每一个编译单位都会拥有一份实体"的东西.
3)如何具现出nonmember以及member template functions,以及static template class members,这些都是"每一恶搞可执行文件中只需要一份实体"的东西,这也就是一般而言template所带来的问题.

注:具现,即表示process将真正的类型和表达式绑定到template相关形式参数上头的操作.

...template的具现行为:
例有如下的template Point class:
template<class Type>
class Point
{
public:
enum Status{unallocated,normalized};
Point(Type x=0.0,Type y=0.0,Type z=0.0);
~Point();
void* operator new(size_t);
void operatpr delete(void*,size_t);
private:
static Point<Type>*freeList;
Type _x,_y,_z;
};

当编译器看到template class声明时,实际上,没有任何反应,上述的static data member并不可用,nested enum也是一样.
使用上述template Point class的enum或是static data member必须Point与某一个类型绑定一起后才可使用,例如:
Point<double>::freeList;
但是,加入定义一个指针:
Point<float>*ptr=0;
这一次程序什么也没发生,因为一个指向class object的指针本身并不是一个class object,编译器不需要知道与该class有关的任何members的数据或object布局,所以"将Point的一个float实体"具现也就没有必要,如今C++ Standard已经禁止在声明一个指向某个template class的指针时,将该template class具现出来.

所以,一个class object的定义,不论是由编译器暗中地做,或是想下面这样明确地做:
const Point<float>origin;
都会导致template class的具现,也就是说,float instantiation的真正对象布局会产生出来.
然而,member functions不应该被"实体"化,只有在member function被使用的时候,C++ Standard才要求它们被"具现"出来,而关于这些函数什么时候被具现出来,有两种策略:
1)编译的时候.
2)在链接的时候.

...template的错误报告:
关于什么样的错误会在编译器处理template声明时被标出来,这里有一部分和template的处理策略有关.cfront对template的处理时完全解析(parse)但不做类型检验:只有在每一个具现操作发生时才做类型检验.所以在一个parsing策略之下,所有语汇(lexing)错误和解析(parsing)错误都会在处理template声明的过程中被标示出来.
在一个十分普遍的替代策略中,template的声明被收集称为一系列的"lexical tokens",而parsing操作延迟,直到真正有具现操作发生时才开始.每当看到一个instantiation发生,这组token就被推往parser,然后调用类型检验等.
同样,nonmember和member template function在具现行为发生之前也一样没有做到完全的类型检验,这是由编译器设计者决定的.

...template中的名称决议方式:
首先必须区分两种意义:一种是C++ Standard所谓的"scope of the template definition",也就是"定义出template"的程序,另一种是C++ Standard所谓的"scope of the template instantiation",也就是"具现出template"的程序.
template之中,对于一个nonmember name的决议结果是根据这个name的使用是否与"用以具现出该template的参数类型"有关而决定的.如果其使用互不相关,那么就以"scope of the template declaration"来决定name,如果其使用互有关联,那么就以"scope of the template instantiation"来决定name.此外,函数的决议结果只和函数的原型有关,和函数的返回值没有关联.
这意味着一个编译器必须保持两个scope contexts:
1)"scope of the template declaration",用以专注于一般的template class.
2)"scope of the template instantiation",用以专注于特定的实体.
编译器的决议算法必须决定哪一个才是适当的scope,然后在其中搜寻适当的name.

...template function的具现行为:
目前的编译器提供了两个策略:一个是编译时期策略,程序代码必须在program text file中备妥可用;另一个是链接时期策略,有一些meta-compilation工具可以导引编译器的具现行为.
然而,编译器必须解决的三个问题:
1)如何找出函数的定义:
方法之一是,包含template program text file,就好像它是个header文件一样.另一个方案是,要求一个文件命名规则.
2)如何能够只具现出程序中的用到的member functions:
解决方案之一就是,根本忽略这项要求,把一个已经具现出来的class的所有member functions都产生出来.另一种策略就是仿真链接操作,检测看看哪一个函数真正需要,然后只为它们产生实体.
3)如何阻止member definitions在多个.o文件中都被具现:
解决方案之一就是产生多个实体,然后从连接器中提供支持,只留下其中一个实体,其余都忽略.另一个办法就是由使用者来导引"仿真链接阶段"的具现策略,决定哪些实体才是需要的.
不论编译时期或链接时期的具现策略,其弱点就是,当template实体被产生出来时,有时候会大量增加编译时间,显然,这将是template functiins第一次具现时的必要条件,然而当那些函数被非必要地再次具现,或是"决定那些函数是否需要在具现"花的代价太大时,编译器的变现令人失望.

注:
1.如果一个virtual function被具现出来,其具现点紧跟在其class的具现点之后.
2.C++ Standard,扩充了对template的支持,允许程序员明确地要求在一个文件中将整个class template具现出来.

7.2 异常处理:
欲支持exception handling,编译器的主要工作就是找出catch子句,以处理被丢出来的exception.这多少需要追踪程序堆栈中的每一个函数的当前作用区域(包括追踪函数中的local class objects),同时,编译器必须提供某种查询exception objects的方法,以知道其实际类型,最后,还需要某种机制用以管理被丢出的object,包括它的产生,存储,可能的解构,清理以及一般存取,也可能有一个以上的objects同时起作用,一般而言,exception handling机制需要与编译器所产生的数据结构以及执行期的一个exception library紧密合作.

...Exception Handling快速检阅:
C++的exception handlin由三个主要的语汇组件构成:
1)一个throw子句,它在程序某处发出一个exception,被丢出的exception可以是内建类型,也可以是使用者自己类型.
2)一个或多个catch子句,每一个catch子句都是一个exception handler,它用来表示说,这个子句准备处理某种类型的exception,并且在封闭的大括号区段中提供实际的处理程序.
3)一个try区段,它被围绕以一系列的叙述句,这些叙述句可能会引发catch子句起作用.
当一个exception被丢出时,控制权会从函数调用中被释放出来,并寻找一个吻合的catch子句,如果没有吻合者,那么默认的处理例程terminate()会被调用,当控制权被放弃后,堆栈中的每一个函数调用也就被推离,这个程序成为unwinding the stack,在每一个函数被推离堆栈之前,函数的local class objects的destructor会被调用.

...对exception handling的支持:
当一个exception发生时,编译系统必须完成以下事情:
1)检验发生throw操作函数.
2)决定throw操作是否发生在try区段中.
3)若是,编译系统必须把exception type拿来和每一个catch子句比较.
4)如果比较吻合,流程控制应该教导catch子句中.
5)如果throw的发生并不在try区段中,或没有一个catch子句吻合,那么系统必须(a)摧毁所有active local objects,(b)从堆栈中将当前的函数"unwind"掉,(c)进行到程序堆栈中的下一个函数中去,然后重复上述步骤2~5.

决定throw是否发生在一个try区段中:
一个函数可以被想象成是好几个区域:
1)try区段以外的区域,而且没有active local objects.
2)try区段以外的区域,但有一个(以上)的active local objects需要解构.
3)try区段以内的区域.
编译器必须标示出以上个区域,并使它们对执行期的exception handling系统有所作用,一个比较好的策略就是构造出program counter-range表格.
当throw操作发生时,当前的program counter值被拿来与对应的"范围表格"进行比较,以决定当前作用中的区域是否在一个try区段中,如果是,就需要找出相关的catch子句,如果这个exception无法被处理,当前的这个函数会从程序堆栈后总被推出,而program counter会被设定为调用端的地址,然后这样的循环再重新开始.

将exception的类型和每一个catch子句的类型做比较:
对于每一个被丢出的exception,编译器必须产生一个类型表述器,对exception的类型进行编码,如果那是一个derived type,则编码内容必须包括其所有base class的类型信息.只编进public base class的类型是不够的,因为这个exception可能被一个member function捕捉,而在一个member function的范围内,在derived class和nopublic base class之间可以转换.

类型描述器是必要的,因为真正的exception是在执行期被处理,其object必须有自己的类型信息,RTTI正是因为支持EH而获得的副产品.

编译器还必须为每一个catch子句产生一个类型表述器,执行期的exception handler会对"被丢出之object的类型表述器"和"每一个catch子句类型表述器"进行比较,直到找到吻合的一个,或是直到堆栈已经被"unwound"而terminate()已被调用.

每一个函数会产生一个exception表格,它描述与函数相关的各区域,任何必要的善后码.以及catch子句的位置.

当一个实际对象在程序执行时被丢出时:
当一个exception被丢出时,exception object会被产生出来并通常放置在相同形式的exception数据堆栈中,从throw端传染给catch子句的是exception object的地址,类型描述器(或是一个函数指针,该函数会传回该exception type有关的类型描述对象),以及可能会有的exception object描述器.

7.3 执行期类型识别(RTTI):

...type-safe downcast:
C++缺乏一个保证类型安全的downcast(向下类型转换操作),欲支持type-safe downcast,在object空间和执行时间上都需要一些额外负担:
1)需要额外的空间以储存类型信息,通常是一个指针指向某个类型信息节点.
2)需要额外的时间以决定执行期的类型,因为,正如其名所示,这需要在执行期才能决定.

冲突发生在两组使用者之间:
1)使用大量多态,并因而需要正统而合法的大量downcast操作.
2)使用内建数据类型以及非多态设备,因而不受各种额外负担所带来的报应.

对于编译器如何区分一个class是一个ADT还是一个支持多态的可继承子类型,一个比较常用的策略就是:经由声明一个或多个virtual functions类区别class声明,其优点是透明优化地将旧程序转换过来,缺点是可能会讲一个其实并非必要的virtual function强迫导入继承体系的base class身上.在C++中,一个具备多态性质的class,正是内涵着继承而来(或直接声明)的virtual functions.
所有多态的class都维护了一个指针vptr,指向virtual function table,与该class相关的RTTI object地址放进virtual table中(通常放在第一个slot),那么额外的负担就降低为:每一个class object只多花费了一个指针,这个指针只被设定一次,它是被编译器静态设定,而不是在执行期有class constructor设定(vptr才是这么设定).

...type-safe dynamic cast:
dynamic_cast运算符可以在执行期决定真正的类型,如果downcast是安全的(base type pointer指向一个derived type object),这个运算符会传回被适当转型过的指针,如果downcast不是安全的,这个运算符会传回0.
type_info是C++ Standard所定义的类型描述器的class名称,该class放置着待索求的类型信息,virtual table的第一个slot内含type_info object的地址:次type_info object有pt所指的class type有关,这两个类型描述器被交给一个runtime library函数,比较之后告诉我们是否吻合,这虽然比static cast昂贵的多,但是比较安全.

...reference并不是pointer:
程序执行中对一个class指针类型施以dynamic_cast运算符,会获得true或false:
1)如果传回真正的地址,表示这个object的动态类型被确认了,一些与类型有关的操作现在可以施行于其上.
2)如果传回0,表示没有志向任何object,意味应该以另一中逻辑施行与这个动态类型未确定的object身上.
dynamic_cast运算符也使用与reference身上,然而对一个non-type-safe cast,其结果不会与指针相同,因为,一个reference不可以想指针那样"把自己这为0就代表了"no object"",弱若将一个reference设为0,会引起一个临时性对象(拥有被参考到的类型)被产生出来,该临时对象的初值为0,这个reference然后被设定为该临时对象的一个别名,因此dynamic_cast用于一个reference时,会有如下事情发生:
1)如果reference真正参考到适当的derivedclass,downcast会被执行而程序可以继续进行.
2)如果reference并不真是某一种derived class,那么,由于不能够传回0,遂丢出一个bad_cast exception.

...typeid运算符:
typeid运算符传回一个const reference,类型为type_info.
type_info object的定义:
class type_info
{
public:
virtual ~type_info();
bool operator==(const type_info&) const;
bool operatpr!=(const type_info&) const;
bool before(const type_info&) const;
private:
type_info(const type_info&);
type_info(const type_info&);
//data members...
};
编译器必须提供最小量信息室class的真实名称,以及在type_info objects之间的某些排序算法,以及某些形式的描述器,用来表现explicit class type和这个class的任何subtypes.

虽然RTTI提供的type_info对于exception handling的支持来说是必要的,但对于exception handling的完整支持而言,还不够,如果再加上额外的一些type_info derived classes,就可以再exception发生时提供有关于指针,函数以及类等等的更详细的信息.

虽然RTTi只使用于多态类,事实上type_info objects也适用于内建类型,以及非多态的使用者自定类型.这对于exception handling的支持有必要.

//=====《inside the c++ object model》学习笔记——全部结束!=================
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: