RTTI

2023-11-11

自从1993年Bjarne Stroustrup 〔注1 〕提出有关C++ 的RTTI功能之建议﹐以及C++ 的
例外处理(exception handling)需要RTTI﹔最近新推出的C++ 或多或少已提供RTTI。然而
,若不小心使用RTTI﹐可能会导致软件弹性的降低。本文将介绍RTTI的观念和近况﹐并说
明如何善用它。

什么是RTTI﹖  
  在C++ 环境中﹐标头档(header file) 含有类别之定义(class definition)亦即包含有
关类别的结构资料(representational information)。但是﹐这些资料只供编译器(compi
ler)使用﹐编译完毕后并未留下来﹐所以在执行时期(at run-time) ﹐无法得知对象的类
别资料﹐包括类别名称、资料成员名称与型态、函数名称与型态等等。例如﹐两个类别﹐
其继承关系如下图:

若有如下指令﹕

            Figure *p; 
            p = new Circle(); 
            Figure &q = *p; 
在执行时﹐p 指向一个对象﹐但欲得知此对象之类别资料﹐就有困难了。同样欲得知q 所
参考(reference) 对象的类别资料﹐也无法得到。

  RTTI(Run-Time Type Identification)就是要解决这困难﹐也就是在执行时﹐您想知道
指针所指到或参考到的对象型态时﹐该对象有能力来告诉您。

  随着应用场合之不同﹐所需支持的RTTI范围也不同。最单纯的RTTI包括﹕

●类别识别(class identification)──包括类别名称或ID。 
●继承关系(inheritance relationship)──支持执行时期的「往下变换型态」(downwar
d casting)﹐亦即动态转型态(dynamic casting) 。

在对象数据库存取上﹐还需要下述RTTI﹕

●对象结构(object layout) ──包括属性的型态、名称及其位置(position或offset)
。 
●成员函数表(table of functions)──包括函数的型态、名称、及其参数型态等。 
  
 其目的是协助对象的I/O 和永存(persistence) ﹐也提供侦错讯息等。

    若依照Bjarne Stroustrup 之建议〔注1 〕﹐C++ 还应包括更完整的RTTI﹕ 
●能得知类别所诞生的各对象 。 
●能参考到函数的原始码。 
●能取得类别的有关线上说明(on-line documentation) 。

其实这些都是C++ 编绎完成时﹐所丢弃的资料﹐如今只是希望寻找个途径来将之保留到执
行期间。然而﹐要提供完整的RTTI﹐将会大幅提高C++ 的复杂度﹗


RTTI可能伴随的副作用  
  RTTI最主要的副作用是﹕程序员可能会利用RTTI来支持其「复选」(multiple-selectio
n)方法﹐而不使用虚拟函数(virtual function)方法。

  虽然这两种方法皆能达到多型化(polymorphism) ﹐但使用复选方法﹐常导致违反著名的
「开放╱封闭原则」(open/closed principle) 〔注2 〕。反之﹐使用虚拟函数方法则可
合乎这个原则,  请看下图﹕

     Circle和Square皆是由Figure所衍生出来的子类别﹐它们各有自己的draw()函数。当
C++ 提供了RTTI﹐就可写个函数如下﹕ 
 void drawing( Figure *p ) 
      { 
           if( typeid(*p).name() == "Circle" ) 
                      ((Circle*)p)  ->  draw(); 
           if( typeid(*p).name() == "Rectangle" ) 
                      ((Rectangle*)p) -> draw(); 
       }

  虽然drawing() 函数也具有多型性﹐但它与Figure类别体系的结构具有紧密的相关性。
当Figure类别体系再衍生出子类别时﹐drawing() 函数的内容必须多加个if指令。因而违
反了「开放╱封闭原则」﹐如下﹕

很显然地﹐drawing() 函数应加以修正。

      想一想﹐如果C++ 并未提供RTTI﹐则程序员毫无选择必须使用虚拟函数来支持draw
ing() 函数的多型性。于是程序员将draw()宣告为虚拟函数﹐并写drawing() 如下﹕

     void drawing(Figure *p) 
                 {     p->draw();      }

     如此﹐Figure类别体系能随时衍生类别﹐而不必修正drawing() 函数。亦即﹐Figur
e体系有个稳定的接口(interface) ﹐drawing() 使用这接口﹐使得drawing() 函数也稳定
﹐不会随Figure类别体系的扩充而变动。这是封闭的一面。而这稳定的接口并未限制Figu
re体系的成长﹐这是开放的一面。因而合乎「开放╱封闭」原则﹐软件的结构会更具弹性
﹐更易于随环境而不断成长。

RTTI的常见的     使用场合  
      一般而言﹐RTTI的常见使用场合有四﹕例外处理(exceptions handling)、动态转型
态(dynamic casting) 、模块整合、以及对象I/O 。

1.例外处理──  大家所熟悉的C++ 新功能﹕例外处理﹐其需要RTTI﹐如类别名称等。

2.动态转型态──  在类别体系(class hierarchy) 中﹐往下的型态转换需要类别继承的
RTTI。 
3.模块整合──  当某个程序模块里的对象欲跟另一程序模块的对象沟通时﹐应如何得知
对方的身分呢﹖知道其身分资料﹐才能呼叫其函数。一般的C++ 程序﹐常见的解决方法是
──在原始程序中把对方对象之类别定义(即存在标头档里)包含进来﹐在编译时进行连
结工作。然而﹐像目前流行的主从(Client-Server) 架构中﹐客户端(client)的模块对象
﹐常需与主机端(server)的现成模块对象沟通﹐它们必须在执行时沟通﹐但又常无法一再
重新编译。于是靠标头文件来提供的类别定义资料﹐无助于执行时的沟通工作﹐只得依赖
RTTI了。 
4.对象I/O ──  C++ 程序常将其对象存入数据库﹐未来可再读取之。对象常内含其它小
对象﹐因之在存入数据库时﹐除了必须知道对象所属的类别名称﹐也必须知道各内含小对
象之所属类别﹐才能完整地将对象存进去。储存时﹐也将这些RTTI资料连同对象内容一起
存入数据库中。未来﹐读取对象时﹐可依据这些RTTI资料来分配内存空间给对象。 
  

RTTI从那里来﹖ 
  上述谈到RTTI的用途﹐以及其副作用。这众多争论﹐使得RTTI的标准迟迟未呈现出来。
也导致各C++ 开发环境提供者﹐依其环境所需而以各种方式来支持RTTI﹐且其支持RTTI的
范围也所不同。  目前常见的支持方式包括﹕ 
●由类别库提供RTTI──例如﹐Microsoft 公司的Visual C++环境。 
●由C++ 编译器(compiler)提供──例如﹐Borland C++ 4.5 版本。 
●由原始程序产生器(code generator)提供──例如Bellvobr系统。 
●由OO数据库的特殊前置处理器(preprocessor)提供──例如Poet系统。 
●由程序员自己加上去。

这些方法皆只提供简单的RTTI﹐其仅为Stroustrup先生所建议RTTI内涵的部分集合而已。
相信不久的将来﹐会由C++ 编译器来提供ANSI标准的RTTI﹐但何时会订出这标准呢﹖没人
晓得吧﹗

程序员自己提供的RTTI  
  通常程序员自己可提供简单的RTTI﹐例如提供类别的名称或识别(TypeID)。最常见的方
法是﹕为类别体系定义些虚拟函数如Type_na() 及Isa() 函数等。请先看个例子﹕ 
 class Figure  { }; 
 class Rectangle : public Figure   { }; 
 class Square : public Rectangle 
        {    int data; 
            public: 
              Square() { data=88; } 
              void Display()  { cout << data << endl; } 
          };

 void main() 
       {   Figure *f = new Rectangle(); 
            Square *s = (Square *)f; 
            s -> Display(); 
       }

这时s 指向Rectangle 之对象﹐而s->Display()呼叫Square::Display() ﹐将找不到data
值。若在执行时能利用RTTI来检查之﹐就可发出错误讯息。于是﹐自行加入RTTI功能﹕

  class Figure 
      {  public: 
             virtual char* Type_na() 
                           {  return "Figure";  } 
              virtual int Isa(char* cna) 
                {  return !strcmp(cna, "Figure")? 1:0;  } 
      }; 
 class Rectangle:public Figure 
     {   public: 
              virtual char* Type_na() 
                     {  return "Rectangle";  } 
              virtual int Isa(char* cna) 
                 {  return !strcmp(cna, "Rectangle")? 
                           1 : Figure::Isa(cna); 
                 } 
        static Rectangle* Dynamic_cast(Figure* fg) 
                 {  return fg -> Isa(Type_na())? 
                           (Rectangle*)fg : 0; 
                 } 
       }; 
  class Square:public Rectangle 
          {    int data; 
       public: 
           Square() { data=88; } 
            virtual char* Type_na() 
                      {  return "Square";  } 
            virtual int Isa(char* cna) 
                {  return !strcmp(cna, "Rectangle")? 
                           1 : Rectangle::Isa(cna); 
                 } 
        static Square* Dynamic_cast(Figure *fg) 
                {  return fg->Isa(Type_na())? 
                           (Square*)fg : 0; 
                 } 
         void Display()  {  cout << "888" << endl;  } 
     }; 
  

虚拟函数Type_na() 提供类别名称之RTTI﹐而Isa() 则提供继承之RTTI﹐用来支持「动态
转型态」函数──Dynamic_cast()。例如﹕ 
       Figure *f  =  new Rectangle(); 
       cout << f -> Isa("Square") << endl; 
       cout << f -> Isa("Figure") << endl;

这些指令可显示出﹕f 所指向之对象并非Square之对象﹐但是Figure之对象(含子孙对象
)。再如﹕ 
       Figure *f;  Square *s; 
       f  =  new Rectangle(); 
       s  =  Square  ==  Dynamic_cast(f); 
       if(!s) 
           cout << "dynamic_cast error!!" << endl; 
此时﹐依RTTI来判断出这转型态是不对的。

类别库提供RTTI  
  由类别库提供RTTI是最常见的﹐例如Visual C++的MFC 类别库内有个CRuntimeClass 类
别﹐其内含简单的RTTI。请看个程序﹕ 
        class Figure:public CObject 
           { 
               DECLARE_DYNAMIC(Figure); 
           }; 
    class Rectangle : public Figure 
           { 
              DECLARE_DYNAMIC(Rectangle); 
           }; 
    class Square : public Rectangle 
        { 
           DECLARE_DYNAMIC(Square); 
           int data; 
         public: 
           void Display()  {  cout << data << endl;  } 
           Square()    {  data=88;  } 
         };

    IMPLEMENT_DYNAMIC(Figure, CObject); 
    IMPLEMENT_DYNAMIC(Rectangle, Figure); 
    IMPLEMENT_DYNAMIC(Square, Rectangle); 
   
Visual C++程序依赖这些宏(Macor) 来支持RTTI。现在就看看如何使用CRuntimeClass 类
别吧﹗如下﹕

          CRuntimeClass *r; 
          Figure *f  =  new Rectangle(); 
          r = f -> GetRuntimeClass(); 
          cout << r -> m_psClassName << endl;

     这就在执行时期得到类别的名称。Visual C++的类别库仅提供些较简单的RTTI──类
别名称、对象大小及父类别等。至于其它常用的RTTI如──数据项的型态及位置(positio
n)等皆未提供。

C++编译器提供RTTI  
  由C++ 语言直接提供RTTI是最方便了﹐但是因RTTI的范围随应用场合而不同﹐若C++ 语
言提供所有的RTTI﹐将会大幅度增加C++ 的复杂度。目前﹐C++ 语言只提供简单的RTTI﹐
例如Borland C++ 新增typeid()操作数以及dynamic_cast<T*>函数样版。请看个程序﹕

   class Figure 
        {   public: 
                virtual void Display(); 
         }; 
   class Rectangle : public Figure   { }; 
   class Square:public Rectangle 
      {    int data; 
          public: 
            Square() {  data=88;  } 
             void Display() {  cout << data << endl;  } 
       };


现在看看如何使用typeid()操作数── 
          Figure *f  =  new Square(); 
          const typeinfo  ty  =  typeid(*f); 
          cout << ty.name() << endl; 
这会告诉您﹕f 指针所指的对象﹐其类别名称是Square。再看看如何使用dynamic_cast<T
*>函数样版── 
      Figure *f;  Square *s; 
      f = new Rectangle(); 
      s = dynamic_cast<Sqiare *>(f); 
      if(!s) 
          cout << "dynamic casting error!!" << endl; 
在执行时﹐发现f 是不能转为Square *型态的。如下指令﹕ 
       Figure *f;  Rectangle *r; 
       f = new Square(); 
       r = dynamic_cast<Rectangle *>(f); 
       if(r)    r->Display(); 
这种型态转换是对的。 
   
RTTI与虚拟函数表 
在C++ 程序中﹐若类别含有虚拟函数﹐则该类别会有个虚拟函数表(Virtual Function T
able﹐简称VFT )。为了提供RTTI﹐C++ 就将在VFT 中附加个指针﹐指向typeinfo对象﹐
这对象内含RTTI资料,如下图:

   由于该类别所诞生之各对象﹐皆含有个指针指向VFT 表﹐因之各对象皆可取出typeinf
o对象而得到RTTI。例如﹐ 
          Figure *f1 = new Square(); 
          Figure *f2 = new Square(); 
          const typeinfo ty = typeid(*f2); 
其中﹐typeid(*f2) 的动作是﹕ 
1.取得f2所指之对象。 
2.从对象取出指向VMF 之指针﹐经由此指针取得VFT 表。 
3.从表中找出指向typeinfo对象之指针﹐经由此指针取得typeinfo对象。

  这typeinfo对象就含有RTTI了。参考下图1,经由f1及f2两指针皆可取得typeinfo对象﹐
所以   typeid(*f2) == typeid(*f1)。


总结  
  RTTI是C++ 的新功能。过去﹐C++ 语言来提供RTTI时﹐大多依赖类别库来支持﹐但各类
别库使用的方法有所不同﹐使得程序的可移植性(portability) 大受影响。然而﹐目前C+
+ 也只提供最简单的RTTI而已﹐可预见的未来﹐当大家对RTTI的意见渐趋一致时﹐C++ 将
会提供更完整的RTTI﹐包括数据项和成员函数的型态、位置(offset)等资料﹐使得C++ 程
序更井然有序﹐易于维护。  

参考资料 
[注1]  Stroustrup B., “Run-Time Type Identification for C++”, Usenix C++ Con
ference, Portland, 1993. 
[注2] Meyer B.,Object-Oriented Software Construction, Prentice Hall, 1988.

 

 




本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

RTTI 的相关文章

  • Android应用底部导航栏(选项卡)实例

    现在很多android的应用都采用底部导航栏的功能 这样可以使得用户在使用过程中随意切换不同的页面 现在我采用TabHost组件来自定义一个底部的导航栏的功能 我们先看下该demo实例的框架图 其中各个类的作用以及资源文件就不详细解释了 还
  • 十分钟利用windows7漏洞破解开机密码

    所有win7系统都使用 首先连按五下Shift键弹出粘滞键提醒 然后我们点击否后关机 启动系统时将其强制关机 虚拟机利用电源关闭虚拟机 自用主机就在开机时长按关机键强制关闭系统 随后启动系统 我们选择启动启动修复 推荐 选择取消即不还原 等
  • Python数据可视化——折线图

    Python数据可视化 折线图 随着数据分析和数据科学的飞速发展 数据可视化成为了越来越重要的一环 而Python作为一门强大的编程语言 其在数据可视化领域也有着不俗的表现 本文将为大家介绍如何使用Python的Matplotlib库创建一
  • 【Transformers】第 6 章:用于标记分类的微调语言模型

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • Vue.config.js常用配置详解

    摘要 本文将介绍Vue config js中常用的配置选项 包括publicPath outputDir devServer chainWebpack等 并提供相应的代码示例 帮助读者更好地理解和配置Vue项目 1 publicPath p
  • 新汽车电子技术图谱

    商业模式 改变传统对于OEM来讲的 卖车即结束 的模式 会员模式 共享模式 租赁模式 运营模式等各种新型的数字出行体验模式 OTA云 远程刷新 远程诊断 远程车控 远程数据上传 第三方App 应用商店 边缘计算 多级云计算 大数据处理 AI
  • Android4.4深入浅出之SurfaceFlinger与Client通信框架(一)

    SurfaceFlinger框架是基于Binder进程间通信机制搭建的 SF作为一个服务进程 用户程序想要跟它通信必然要经过Binder机制 首先说一下 用户要跟SF通信 那么SF必须出现在ServiceManager中 因为SF也是一个服
  • ROS STAGE教程1

    默认路径opt ros kinetic share 下有stage 和 stage ros 到该路径下可运行 rosrun stage ros stageros rospack find stage ros world willow err
  • STM32+HC-05蓝牙模块学习与使用

    HC 05蓝牙串口通信 HC05模块是一款高性能主从一体蓝牙串口模块 是一种集成蓝牙功能的PCBA板 用于短距离无线通信 十分方便 从某宝商家那里可以看到 蓝牙可以使用多种方法使用 这里我使用的是蓝牙主机连接 所以我们这里需要准备的器件 两
  • 【python学习】函数式编程和高阶函数 map filter reduce lambda表达式 sorted 闭包 装饰器

    函数式编程就是一种抽象程度很高的编程范式 纯粹的函数式编程语言编写的函数没有变量 因此 任意一个函数 只要输入是确定的 输出就是确定的 这种纯函数我们称之为没有副作用 而允许使用变量的程序设计语言 由于函数内部的变量状态不确定 同样的输入

随机推荐