C++对象模型——执行期类型识别(第七章)

来源:互联网 发布:ib网络是什么意思啊 编辑:程序博客网 时间:2024/06/13 06:23

7.3    执行期类型识别 (Runtime Type Identification,RTTI)

    下面这样的转型形式:
pfct pf = pfct(pt);
    被称为downcast(向下转型),因为它有效地把一个base class 转换至继承结构的末端,变成其derived classes中的某一个.Downcast有潜在性的危险,因为它遏制了类型系统的作用.

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

    C++被吹毛求疵的一点就是,它缺乏一个保证安全的downcast(向下转型操作).只有在"类型真的可以被适当转型"的情况下,才能够执行downcast.一个type-safe downcast必须在执行期对指针有所查询,看看它是否指向它所展现的object的真正类型.因此,欲支持type-safe downcast,在object空间和执行时间上都需要一些额外负担:
    需要额外的空间以储存类型信息,通常是一个指针,指向某个类型信息节点
    需要额外的时间以决定执行期的类型,因为,这需要在执行期才能决定.

    这样的机制面对下面这样平常的C结构,会如何影响其大小,效率,以及链接兼容性呢?
char *winnie_tbl[] = {"rumbly in tummy", "oh, bother"};
    冲突发生在两组使用者之间:
    1.程序员大量使用多态,并因而需要正统而合法的大量downcast操作.
    2.程序员使用内建数据类型以及非多态设备,因而不受各种额外负担所带来的报应.
    理想的解决方案是,为两派使用者提供正统而合法的需要.
    一个策略是经由声明一个或多个 virtual function来区别 class 声明.其优点是透明化地将旧有程序转换过来,只要重新编译就好.缺点则是可能会将一个其实并非必要的 virtual function强迫导入继承体系的base class 上.这正是RTTI机制所支持的策略,在C++中,一个具备多态性质的 class,正是内含着继承而来的 virtual functions.
    从编译器的角度来看,这个策略还有其他优点,就是大量降低额外负担.所有polymorphic classes 的objects都维护了一个指针,指向 virtual function table.只要把与该 class 相关的RTTI object地址放进 virtual table中(通常放在第一个slot),那么额外负担就降低为:每一个 class object 只多花费一个指针.这个指针只需被设定一次,它是被编译器静态设定,而不是在执行期由 class constructor设定.

Type-Safe Dynamic Cast (保证安全的动态类型转换)

    dynamic_cast 运算符可以在执行期决定真正的类型.如果downcast是安全的(也就是说,如果base type pointer指向一个derived class object),这个运算符会传回被适当转型过的指针.如果downcast不是安全的,这个运算符会传回0.下面就是如何重写原本的cfront downcast:
typedef type *ptype;typedef fct *pfct;simplity_conv_op(ptype pt) {    if (pfct pf = dynamic_cast<pfct>(pt)) {        // ... process pf    } else {        ...    }}
    什么是dynamic_cast 的真正成本呢?pfct的一个类型描述器会被编译器发生出来.由pt指向的 class object类型描述器必须在执行期通过vptr取得.下面就是可能的转换:
// 取得pt的类型描述器((type_info *)(pt->vptr[0]))->type_descriptor;
    type_info是C++ standard所定义的类型描述器的 class 名称,该 class 中放置着待索求的类型信息.virtual table的第一个slot内含type_info object的地址;此type_info object与pt所指的 class type有关.这两个类型描述器被交给一个runtime library函数,比较后查看是否吻合.很显然这比 static cast 昂贵的多,但却安全得多.

References并不是Pointers

    程序执行中对一个 class 指针类型施以 dynamic_cast 运算符,会获得 true 或 false:
    如果传回真正的地址,表示这个object的动态类型被确认了,一些与类型有关的操作现在可以施行于其上.
    如果传回0,表示没有指向任何object,意味着应该以另一种逻辑施行于这个动态类型未确定的object上.

    dynamic_cast 运算符也适用于reference上.然而对于一个non-type-safe cast,其结果不会与施行于指针的情况相同.为什么?一个reference不可以像指针那样"把自己设为0便代表了'no object'";若将一个reference设为0,会引起一个临时性对象被产生出来,该临时对象的初值为0,这个reference然后被设定成为该临时对象的一个别名(alias).因此当 dynamic_cast 运算符施行于一个reference时,不能够提供对等于指针情况下的那一组 true/false.取而代之的是,会发生下列事情:
    如果reference真正参考到适当的derived class(包括下一层或下下一层或...),downcast会被执行而程序可以继续进行.
    如果reference并不真正是某一种derived class,那么,由于不能够传回0,就丢出一个bad_cast exception.
    下面是重新实现后的simplify_conv_op函数,参数改为一个reference:
simplify_conv_op(const type &rt) {    try {        fct &rf = dynamic_cast<fct &>(rt);        // ...    } catch (bad_cast) {        // ... mumble ...    }}
    其中执行的操作十分理想地表现出某种exception failure,而不只是简单的控制流程.

Typied运算符

    使用typeid运算符(typeid是C++的关键字,用来获取表达式的类型,以std::type_info表示结果,可能抛出std::bad_typeid.当操作数非多态类(引用)类型在编译时即可确定结果,否则需要在运行时取得结果,即RTTI),就有可能以一个reference达到相同的执行期替代路线:
simplify_conv_op(const type &rt) {    if (typeid(rt) == typeid(fct)) {        fct &rf = static_cast<fct &>(rt);        // ...    } else {        ...    }}
    虽然一个明显较佳的实现策略是在gen和fct class 中引进 virtual function.
    typeid 运算符传回一个 const reference,类型为 type_info,在先前测试中出现的equality运算符,其实是一个被overloaded的函数:
bool type_info::operator==(const type_info &) const;
    如果两个type_info objects相等,这个equality运算符就传回 true.
    编译器必须提供的最小量信息是 class 的真实名称,以及在type_info objects之间的某些排序算法,以及某些形式的描述器,用来表现 explicit class type和这个 class 的任何subtypes.
    虽然RTTI提供的type_info对于exception handling的支持来说是必要的,但对于exception handling的完整支持而言,还是不够.如果再加上额外的一些type_info derived classes,就可以在exception发生时提供有关于指针,函数及类等等的更详细信息.
    type_info objects也适用于内建类型,以及非多态的使用者自定类型.这对于exception handling的支持有必要,例如:
int ex_errno;...throw ex_errno;
    其中 int 类型也有自己的type_info object.下面就是使用法:
int *ptr;...if (typeid(ptr) == typeid(int *))    ...
    在程序中使用typeid,像这样:
int iva;...typeid(ival) ...;
    或是使用typeid(type),像这样:
typeid(double) ...;
    会传回一个 const type_info&.这与先前使用多态类型的差别在于,这时候的type_info object是静态取得的,而非执行期取得.一般的实现策略是在需要时才产生type_info object,而非程序一开头就产生.
0 0
原创粉丝点击