C++对象模型学习笔记7 站在对象模型的尖端

Template

  • {P280} template甚至被使用于一项所谓的template metaprograms技术:class expression templates将在编译时期而非执行期被评估,因而带来重要的效率提升。(模板元编程 wow...
  • {P284} 只有在member functions被使用的时候,C++标准才要求它们被实例化,之所以由使用者来主导“实例化”规则,主要有两个原因:
    1. 空间和时间效率的考虑,浪费编译时间和目标文件大小。
    2. 尚未实现的机能,并不是所有类型都能支持template所需要的所有运算符。
  • {P285} 什么时候“实例化”? 目前流行两种策略:
    1. 编译的时候
    2. 链接的时候
Template中的错误报告
  • 在模板定义时,有些错误并不会检查,只能等到实例化的时候才能确定下来。
Template中的名称决议法
  • 区分两种意义:
  • 一种是scope of the template definition,即定义出template的程序端:
    //scope of template definition
    extern double foo(double);
     
    template <typename type>
    class ScopeRules
    {
    public:
        void init()
        {
            _member = foo(_val);
        }
        type type_dependency()
        {
            return foo(_member);
        }
    private:
        int _val;
        type _member;
    };
  • 另一种是"scope of the template instantiation",即实例化template的程序端。
    //scope of template instantiation
    extern int foo(int);
    ScopeRules<int> sr0; 
  • {P290-291} 假设我们有sr0.invariant() 进而会调用哪一个foo呢? 乍一看,实例化为int,当然是调用int版本的foo了。但实际上调用的是double版本的foo。原因:对于一个nonmember name的决议结果,是根据这个name的使用是否与“用以实例化该template的参数类型”有关而决定的。 如果其使用互不相关,那么就以"scope of T declaration"来决定,否则以"scope of T instantiation"来决定。
  • 这里的invariant调用的是_member = foo(_val);,而_val的类型已经确定了,是int,是一个类型不会变动的template class member。
  • 如果这里用的是sr0.type_dependent();,那么因为它调用的是foo(_member),这里的_member的类型是跟模板参数类型有关系的,所以选择的是int版本的。

异常处理(EH)

  • {P297} 欲支持EH,编译器的主要工作是:
  1. 找出catch子句,以处理被抛出来的exception。这多少需要追踪程序堆栈中的每一个函数的当前作用域(包括追踪函数中的local class object情况)
  2. 编译器必须提供某种查询异常对象的方法,以知道其实际类型(这直接导致某种形式的执行期类型识别,也就是RTTI)
  3. 最后,编译器还需要某种机制管理被抛出的对象,包括它的产生,存储,可能的析构(如果有相关的dtor),清理以及一般存取。编译器必须提供某种查询异常对象的方法,以知道其实际类型(RTTI来源)
  • {P297} 一般而言,EH机制需要与编译器产生的数据结构以及执行期的一个exception library (EH runtime?) 紧密合作。

EH快速检阅 {P298}
  1. 一个throw子句。抛出异常,其类型可以是内建类型,也可以是自定义类型。
  2. 一个或者几个catch子句。每个catch子句都是一个exception handler。表示准备要处理某种类型的异常,并提供实际处理程序。
  3. 一个try区段。它被围绕以一系列的语句,这些语句可能会引发catch子句起作用。
  • 当一个异常被抛出时,控制权会从函数调用中释放出来,并寻找一个吻合的catch子句(landing pad)。如果都没有吻合者,那么默认的处理例程terminate()会被调用。当控制权被放弃后,堆栈中的每一个函数调用也就被popped up。这个程序被称为unwinding the stack。(退栈?)在每一个函数被退栈之前,函数的local class objects的dtor会被调用。
对EH的支持 {P303}
  • 当一个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区段中
  • {P303} 一个函数可以被想像为好几个区域:
    1. try区段以外的区域,且没有active local objects
    2. try区段以外的区域,但有active local objects需要析构
    3. try区段以内的区域
  • 编译器必须标识出以上区域,并使它们对执行期的EH系统有所作用。一个很棒的策略是构造出program counter-range表格。当throw发生时,当前的PC值跟对应的范围表格进行对比,以决定面前作用中的区域归属。
将exception的类型和每一个catch子句类型比较
  • {P304} 对每一个被抛出来的exception,编译器必须产生一个类型描述器,对exception的类型进行编码。如果是一个derived type,编码内容必须包括其所有base class的类型信息。
  • 类型描述器是必要的,RTTI正是因为支持EH而获得的副产品。
  • 每个函数会产生一个exception表格,它描述与函数相关的各区域、任何必须的善后处理代码以及catch子句的位置。

执行期类型识别 RTTI

Type-Safe Downcast (保证安全的向下转换操作)

  • {P310} C++被吹毛求疵的一点是:它缺乏一个保证安全的downcast。只有在类型真的可以被适当转换的情况下,你才能够执行downcast。一个type-safe downcast必须在执行期对指针有所查询,查查看它是否指向它所展现(表达)之object的真正类型。
  • 危险在于,如果一个父类指针原本指向的是一个父类对象,被强转后子类指针后,其实它不拥有一些子类的数据或行为,此时当成子类指针来用就不完全了。
  • 欲支持此机制的话,在空间和时间上都需要额外的负担:
    1. 需要额外的空间存储类型信息(type info),通常是一个指针,指向某个类型信息节点。
    2. 需要额外的时间以决定执行期的类型(runtime type)。
  • {P310} 对于那些大量使用多态的程序来说,需要正统而合法的大量downcast操作;而内建数据类型或者非多态类型,不应该受各种额外负担带来的不良后果。一个策略是添加新的关键字,区别那些支持多态的子类型,但缺点必须修改程序。另一种策略是通过声明一个或者多个virt function来区别,优点通过将指向类相关的运行时信息的指针存入虚函数表,可以大大降低额外负担。

保证安全的动态转型

  • dynamic_cast运算符可以在执行期决定真正的类型。如果downcast是安全的,则传回被适当转换过的指针。否则,会传回零。
  • 什么是dynamic_cast的真正成本?
  1. 假设对应的代码为pfct pf = dynamic_cast<pfct>(pt);,首先必须在执行期通过vptr获得pt所指向的class object类型描述器。eg:
 // type_info是C++标准定义的类型描述器的class名
((type_info*)(pt->vptr[0]))->_type_descriptor;
  1. 将pfct的类型描述器和pt的类型描述器交给一个runtime library函数,去判断是否吻合。很显然,这比static cast来的昂贵,但却安全得多。
  • 推论(我的): dynamic_cast需要程序启用RTTI机制。

References 并不是 Pointers

  • {P313} dynamic_cast 也适用于ref身上。然而对于一个non-type-safe cast,其结果不会与指针相同,为什么?
  • 因为对于指针来说,不适用则返回空。==但对于引用不可能为空,若将一个ref设为0,会引起一个临时对象的产生,该临时对象的初值为0。==而这个ref被设定为该临时对象的一个别名。
  • 所以对于ref,不能够提供等同于指针的非空或者空,而会发生下列事情:
  1. 如果可以安全donwcast,与指针一样。
  2. 否则,抛出bad_cast异常。
  • 举个例子
simplify_conv_op(const type &rt)
{
    try {
       fct &rf = dynamic_cast<fct&>(rt);
    } catch (bad_cast) {
       // ... mumble ...
    }
}

Typeid运算符

  • 对于上面的ref的问题,我们是否有更好的办法来处理呢?
  • {P314} 使用typeid运算符,就能以一个ref得到类似的执行期处理。
  • 一些代码示例:
simplify_conv_op(const type &rt)
{
    if (typeid(rt) == typeid(fct)) {
      fct &rf = static_cast<fct&>(rt);
    } else{ ... }
}

class type_info {
public:
    virtual ~type_info();
    bool operator==(const type_info&) const;
    bool operator!=(const type_info&) const;
    bool before(const type_info&) const;
    const char* name() const; // class原始名称
private:
    //prevent memberwise init and copy
    type_info(const type_info&);
    type_info& operator=(const type_info&);

    //data members
};
  • {P317} 事实上type_info不仅适用于多态类,它也适用于内建类型以及非多态的用户自定义类型。这对EH的支持是有必要的。eg:
  int ex_errno;
  ...
  throw ex_errno;
  ...
  int *ptr;
  if (typeid(ptr) == typeid(int*))
  ...
  int ival;
  typeid(ival)
  ...
  typeid(double).. => 会传一个const type_info&
  • 与先前的多态类型的差异在于,这时候的type_info object是静态取得的,而无需等到执行期才能取得。一般的实现策略是在需要时才产生type_info object,而非程序一开头就产生之。推论(我的):type_info table是编译器按需产生的,否则这么多类型,浪费编译时间和空间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值