深度探索c++对象模型之执行期的类型识别RTTI

在cfront中,用来表现一个程序的内部类型体系,看起来像这样:

//程序层次结构的根类(root class)
class node{...};

class type : public node{...};

//两个函数陈述
class fct : public type{...};
class gen : public type{...};
其中第二个gen是generic的缩写,意思是通用的,用来表现一个重载函数。
然后只要我们有一个变量,或者是类型为type*的成员,并知道它代表一个函数,那么我们都必须决定其特定的派生类是fct或是gen。在cfront2之前,除了析构函数之外,唯一不能够被重载的是就是conversion转换运算符。例如:
class String{
public:
  operator char*();
  //...
}
在2.0导入常量成员函数之前,conversion运算符不能够重载,因为它们不使用参数。直到引进了=常量成员函数之后才可以,所以像下面的声明就有可能了:
class String{
public:
  operator char*();
  operator char*() const;
  //...
}//cfront2.0或之后版本

也就是说,在cfront2.0版本之前,以一个explicit cast【显式转换】来存取派生对象总是安全快速的,比如下面:

typedef type *ptype;
typedef fct *pfct;

simplify_conv_op( ptype pt ){
//转换运算符可以作用于fct上面
pfct pf = (pfct)pt;
...
} 
在常量成员函数引入之前,上面的代码是正确的,但在常量成员函数引入之后,上面的代码就不对了,这是因为String class声明的改变,因为char *conversion运算符现在被内部视为一个gen而不是fct。

像这样的转换形式【pfct pf = (pfct)pt; 】被称为向下转型(downcast),因为它能把一个基类转换到继承结构是末端,变成其派生类中的某一个;向下转型存在着潜在危险:它遏制了类型系统的作用,不正确的使用可能会带来错误的解释,如果它是一个read操作的话,而如果它是一个write操作,则可能会腐蚀掉程序内存。在我们上面的例子中,一个指向gen object的指针被转型为一个指向fct object的指针,所以后续对pf的使用都是不正确的,除非只检查它是否是0,或仅仅拿来和其它指针比较。


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

c++被诟病的其中一点就是,她缺乏一个保证安全的向下转型;只有在类型真的可以被适当转型的情况下,你才能够执行downcast。一个type-safe downcast必须在执行期对指针有所查询,看看它是否指向它所表达的对象的真正类型,这无疑会让object的空间和执行时间上都增添一些额外负担:

1):需要额外的空间用来储存类型信息,通常是一个指针,指向某个类型信息节点。

2):需要额外的时间用来决定执行期的类型,因为很多对象的实际类型需要在执行期才能决定。

在这样的机制下对下面这种平常的C结构,会如何影响其大小、效率以及链接兼容性呢?

char *winnie_tbl[] = { "rumbly in my tummy", "oh,bother" }; 
很明显,它所导致的空间效率的不良影响会很大。

冲突发生在两组使用者之间:

1):程序员大量使用polymorphism(多态),并因此需要大量合法的downcast操作。

2):程序员使用內建数据类型如int等,因此不受各种额外负担所带来的的报应。

c++的RTTI机制提供一种安全的downcast设备,但只对那些使用继承和动态绑定类展现polymorphism(多态)的类型有效。编译器该如何分辨这些?能否只依靠class的定义就能决定这个类究竟是独立的抽象数据类型亦或一个支持多态的可继承子类型?策略之一就是导入一个新的关键词,优点是可以清楚的识别出支持新特性的类型,缺点是必须翻新旧程序!

但还有另外一个策略:经由声明一个或多个virtual functions(虚函数们)来区别类声明,这个策略的优点是透明化的把旧程序转换过来,只要重新编译好就行。缺点则是有可能会把一个并非必要的虚函数强行导入继承体系的基类身上。在c++中,一个具备polymorphism(多态)性质的类,正是内含着继承而来的虚函数们。

从编译器的角度来看,这个策略还会大量降低额外负担,所有的polymorphism类们都维护了一个虚表指针,指向虚函数表。我们只要把与该类相关的TRRI object放入虚函数表中(通常放在第一个slot中),那么额外负担就降低为:每一个类对象只多花费一个指针,这个指针只需被设定一次,还是被编译器静态设定,而不是在执行期由class constructor设定,因为在class constructor(类构造器)中被设定的,是虚表指针。


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

dynamic-cast运算符可以在执行期决定对象真正的类型,如果downcast是安全的,即一个基类的指针实际指向的一个派生类类型对象,这个运算符会传回被适当转型过的指针。如果downcast不安全,这个运算符就传回0。下面就是我们如何重写cfront downcast(当然,我们的pt实际类型可能是gen,也可能是fct。比较受欢迎的方法是使用虚拟函数:函数的真正类型被封装起来。程序清晰且容易扩充,能够处理更多类型):

typedef type )ptype;
typedef fct *pfct;

simplify_conv_op( ptype pt ){
  if( pfct fp = dynamic_cast< pfct >( pt ) )
  {...}
  else {...}
}
那么什么是dynamic_cast的做了什么呢?pfct的一个类型描述器会被编译器产生出来,而pt指向的类对象的类型描述器就必须在执行期由vptr获得,下面就是可能的转换:
//取出pt的类型描述器
((type_info*)(pt->vptr[0]))->_type_descriptor;
其中,type_info是c++标准所定义的类型描述器的class名称,该class中存有待索求的类型信息,而虚函数表的第一个slot(vptr[0])中是type_info 对象的地址;在例子中,此typeinfo对象与pt所指的class type有关,而这两个类型描述器会被交给一个runtime library(运行时库)函数比较,然后该函数返回结果告诉我们它们是否吻合。以上种种,都要比静态转换麻烦的多,但同样的,也安全的多。

最初对于运行时转换的支持提议中,并未引进任何关键字或语法,下面这样的转换操作【pfct pf = pfct(pt); 】究竟是静态转型还是动态转型,必须要看pt是不是指向一个多态的类对象而定。





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值