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

...这一章主要讨论三个著名的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》学习笔记——全部结束!=================

原文地址:https://www.cnblogs.com/suiyu/p/2746092.html