7.3 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,而不只是简单的控制流程。

typeid 运算符

使用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{
public:
	virtual ~type_info();
	bool operator==( const typeid_info& );
	bool operator!=( const typeid_info& );
	bool before( const typeid_info& );
	const char* name() const;	// 传回class原始名称
private:
	// 阻止memberwise init and copy
	type_info(const type_info& );
	type_info& operator=(const type_info& );
}

编译器必须提供的最小量信息是 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(expression),像这样:

int iva;
typeid(ival) ...;

或是使用typeid(type),像这样:

typeid(double) ...;

会传回一个 const type_info&.这与先前使用多态类型的差别在于,这时候的type_info object是静态取得的,而非执行期取得.一般的实现策略是在需要时才产生type_info object,而非程序一开头就产生.

posted @ 2021-05-02 00:03  点|滴  阅读(14)  评论(0)    收藏  举报