RTTI

时间:2021-08-08 04:42:21

RTTI(Run-Time Type Identification,通过运行时类型识别)程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。

 

编辑本段RTTI介绍

  RTTI提供了以下两个非常有用的操作符:

 

  (1)typeid操作符,返回指针和引用所指的实际类型;

 

  (2)dynamic_cast操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

 

  面向对象的编程语言,象C++,Java,delphi都提供了对RTTI的支持。 本文将简略介绍 RTTI 的一些背景知识、描述 RTTI 的概念,并通过具体例子和代码介绍什么时候使用以及如何使用 RTTI;本文还将详细描述两个重要的 RTTI 运算符的使用方法,它们是 typeid 和 dynamic_cast。

 

  其实,RTTI 在C++中并不是什么新的东西,它早在十多年以前就已经出现了。但是大多数开发人员,包括许多高层次的C++程序员对它并不怎么熟悉,更不用说使用 RTTI 来设计和编写应用程序了。

 

  一些面向对象专家在传播自己的设计理念时,大多都主张在设计和开发中明智地使用虚拟成员函数,而不用 RTTI 机制。但是,在很多情况下,虚拟函数无法克服本身的局限。每每涉及到处理异类容器和根基类层次(如 MFC)时,不可避免要对对象类型进行动态判断,也就是动态类型的侦测。如何确定对象的动态类型呢?答案是使用内建的 RTTI 中的运算符:typeid 和 dynamic_cast。

 

  在C++中存在虚函数,也就存在了多态性,对于多态性的对象,在程序编译时可能会出现无法确定对象的类型的情况。当类中含有虚函数时,其基类的指针就可以指向任何派生类的对象,这时就有可能不知道基类指针到底指向的是哪个对象的情况,类型的确定要在运行时利用运行时类型标识做出。为了获得一个对象的类型可以使用typeid函数,该函数反回一个对type_info类对象的引用,要使用typeid必须使用头文件<typeinfo>,因为typeid是一个返回类型为typ_info的引用的函数所以这里有必要先介绍一下type_info类

typid函数

  该函数的主要作用就是让用户知道当前的变量是什么类型的,比如使用typid(a).name()就能知道变量a是什么类型的。因为typid()函数是一个反回类型为typid_info类型的函数,所以下面先对type_info类作下介绍

type_info类

  该类的具体实现方式依编译器而定,但一般都有如下的成员定义

 

  class type_info

 

  {private:

 

  type_info(const type_info &);

 

  type_info& operator =(const type_info&); //type_info类的复制构造函数和赋值运算符是私有的。

 

  public:

 

  virtual ~type_info(); //析构函数

 

  bool operator = =(const type_info&) const; //在type_info类中重载了= =运算符,该运算符可以比较两个对象的类型是否相等。

 

  bool operator !=(const type_info&)const; //重载的!=运算符,以比较两个对象的类型是否不相等

 

  const char * name() const; //使用得较多的成员函数name,该函数反回对象的类型的名字。前面使用的typeid(a).name()就调用了该成员函数

 

  bool before(const type_info&);};

 

  因为type_info类的复制构造函数和赋值运算符都是私有的,所以不允许用户自已创建type_info的类,比如type_info A;错误,没有默认的构造函数。唯一要使用type_info类的方法就是使用typeid函数。

typeid函数怎样创建type_info类的对象

  该函数反回type_info类对象的引用,即形式为type_info& typid();因此也可以说typid函数是type_info类的一个引用对象,可以访问type_info类的成员。但因为不能创建type_info类的对象,而typeid又必须反回一个类型为type_info类型的对象的引用,所以怎样在typeid函数中创建一个type_info类的对象以便让函数反回type_info类对象的引用就成了问题。这可能是把typid函数声明为了type_info类的友元函数来实现的,默认构造函数是私有的并不能阻止该类的友元函数创建该类的对象。所以typeid函数如果是友元的话就可以访问type_info类的私有成员,从而可以创建type_info类的对象,从而可以创建反回类型为type_info类的引用。举个例子class A{private:A(){} A(const A&){} A& operator =(const A&){} friend A& f();};这里把类A的默认构造函数,复制构造函数和赋值操作符定为私有从而防止创建类A的对象,但函数f()是类A的友元,所以在函数f()中可以创建类A的对象。同时为了实现函数f()反回的对象类型是A的引用,就必须在函数f中创建一个类A的对象以作为函数f的反回值,比如函数f可以这样定义A& f(){A ma; cout<<”f”<<endl; return ma}。

 

  因为typeid函数是type_info类的对象,也就是说可以用该函数访问type_info类的成员,即type_info类中重载的= =和!=运算符,name()和before()成员函数,比如typid(a).naem()和typid(a)= =typid(b)等等。

typeid函数的使用原理

  该函数的形式为type_info& typeid(object)其中object是任何类型的对象,可以是内置类型和用户创建的类类型。可以看出typeid即是一个函数,同时他也是type_info类的对象,即typeid可以访问类type_info类的成员,也可以做为一个单独的函数来使用。做个简单的例子,比如

 

  class A{private: A(){b=3;cout<<”A”<<endl;} //私有的默认构造函数

 

  public: void name(){cout<<”NA”<<endl;} int b;

 

  friend A f();}; //函数f()是类A的友元,因此在f中可以创建类A的对象。

 

  A f() //函数f()在这里即是类A的一个对象,也是一个单独的函数。

 

  { A m; //创建类A的对象,因为函数f是类A的友元,因此可以创建类A的对象

 

  cout<<”F”<<endl; return m;}

 

  main()

 

  { f().name(); //函数f()作为类A的对象使用,这里要注意程序的执行顺序,首先执行函数f()中的语句A m,因此调用类A的默认构造函数输出A,然后执行A m;后面的语句,输出F,再然后调用类A中的成员函数name输出NA.

 

  f(); } //函数f()单独作为函数使用。

 

  我们创建一个类A,其中A的默认构造函数是私有的,也就是说不能用默认构造函数创建类A的对象。函数f()是类A的友元,且反回一个类A的对象,因为f()函数是类A的友元,所以在函数f中可以用默认构造函数创建类A的对象,这时函数f()同时是一个函数,也是类A的对象,因此也可以访问类A中的成员。

typeid函数使用方式

  1)、使用type_info类中的name()成员函数反回对象的类型的名称。其方法为:typeid(object).name()其中object是要显示的对象的类型名,该函数反回的名字因编译器而定。这里要注意的就是使用方式一中提到的虚函数类型的问题,即如果有类A,且有虚函数,类B,C,D都是从类A派生的,且都重定义了类A中的虚函数,这时有类A的指针p,再把对象类B的对象的地址赋给指针p,则typeid(p).name()将反回的类型将是A*,因为这里的p表示的是一个指针,该指针是类型为A的指针,所以反回A*,而typeid(*p).name()将反回B,因为指针p是指向类B的对象的,而*p就表示的是类B的对象的类型,所以反回B。

 

  2)、使用type_info类中重载的= =与!=比较两个对象的类型是否相等。使用该方法需要调用类type_info中重载的= =和!=操作符,其使用方法为typid(object1)= =typid(object2);如果两个对象的类型相等则反回1,如果不相等则为0。这种使用方法通常用于比较两个带有虚函数的类的对象是否相等,比如有类A,其中定义有虚函数,而类B,类C,类D,都是从类A派生而来的且重定义了该虚函数,这时有个类A的指针p和p1,按照虚函数的原理,基类的指针可以指向任何派生类的对象,在这时就有可能需要比较两个指针是否指向同一个对象,这时就可以这样使用typeid了,typeid(*p)= =typeid(*p1);这里要注意的是typeid(*p)与typeid(p)是指的不同的对象类型,typeid(p)表示的是p的类型,在这里p是一个指针,这个指针指向的是类A的对象,所以p的类型是A*,而typeid(*p)则不一样,*p表示的是指针p实际所指的对象的类型,比如这里的指针p指向派生类B,则typeid(*p)的类型为B。所以在测试两个指针的类型是否是相等时应使用*p,即typeid(*p)= =typeid(*p1)。如果是typeid(p)= =typeid(p1)的话,则无论指针p和p1指向的什么派生类对象,他们都是相等的,因为都是A *的类型。

强制类型转换运算符

  C++有四种强制类型转换符,分别是dynamic_cast,const_caststatic_castreinterpret_cast。其中dynamic_cast与运行时类型转换密切相关,在这里我们先介绍dynamic_cast,其他三种在后面介绍。

dynamic_cast强制转换运算符

  该转换符用于将一个指向派生类的基类指针或引用转换为派生类的指针或引用,注意dynamic_cast转换符只能用于含有虚函数的类,其表达式为dynamic_cast<类型>(表达式),其中的类型是指把表达式要转换成的目标类型,比如含有虚函数的基类B和从基类B派生出的派生类D,则B *pb; D *pd, md; pb=&md; pd=dynamic<D*>(pb); 最后一条语句表示把指向派生类D的基类指针pb转换为派生类D的指针,然后将这个指针赋给派生类D的指针pd,有人可能会觉得这样做没有意义,既然指针pd要指向派生类为什么不pd=&md;这样做更直接呢?有些时候我们需要强制转换,比如如果指向派生类的基类指针B想访问派生类D中的除虚函数之外的成员时就需要把该指针转换为指向派生类D的指针,以达到访问派生类D中特有的成员的目的,比如派生类D中含有特有的成员函数g(),这时可以这样来访问该成员dynamic_cast<D*>(pb)->g();因为dynamic_cast转换后的结果是一个指向派生类的指针,所以可以这样访问派生类中特有的成员。但是该语句不影响原来的指针的类型,即基类指针pb仍然是指向基类B的。如果单独使用该指针仍然不能访问派生类中特有的成员。一般情况下不推见这样使用dynamic_cast转换符,因为dynamic_cast的转换并不会总是成功的,具体情况在后面介绍。

dynamic_cast的注意事项

  dynamic_cast转换符只能用于指针或者引用。dynamic_cast转换符只能用于含有虚函数的类。dynamic_cast转换操作符在执行类型转换时首先将检查能否成功转换,如果能成功转换则转换之,如果转换失败,如果是指针则反回一个0值,如果是转换的是引用,则抛出一个bad_cast异常,所以在使用dynamic_cast转换之间应使用if语句对其转换成功与否进行测试,比如pd=dynamic_cast<D*>(pb); if(pd){…}else{…},或者这样测试if(dynamic_cast<D*>(pb)){…}else{…}。

const_cast操作符

  其表达式为const_cast<类型>(表达式),其中类型指要把表达式转换为的目标类型。该操作符用于改变const和volatile,const_cast最常用的用途就是删除const属性,如果某个变量在大多数时候是常量,而在某个时候又是需要修改的,这时就可以使用const_cast操作符了。const_cast操作符不能改变类型的其他方面,他只能改变const或volatile,即const_cast不能把int改变为double,但可以把const int改变为int。const_cast只能用于指针或引用。const_cast的用法举例比如:int a=3; const int *b=&a; int* c; c=const_cast<int*>(b); *c=4; cout<<a<<*c;这时输出两个4,如果不使用const_cast转换符则常量指针*c的值是不能改变的,在这里使用const_cast操作符,通过指针b就能改变常量指针和变量a的值。

static_cast操作符

  该操作符用于非多态类型的转换,任何标准转换都可以使用他,即static_cast可以把int转换为double,但不能把两个不相关的类对象进行转换,比如类A不能转换为一个不相关的类B类型。static_cast本质上是传统c语言强制转换的替代品。

reinterpret_cast操作符

  该操作符用于将一种类型转换为另一种不同的类型,比如可以把一个整型转换为一个指针,或把一个指针转换为一个整型,因此使用该操作符的危险性较高,一般不应使用该操作符。

 

  9、使用 typeid 要注意一个问题,那就是某些编译器(如 Visual C++)默认状态是禁用 RTTI 的,目的是消除性能上的开销。如果你的程序确实使用了 RTTI,一定要记住在编译前启用 RTTI。使用 typeid 可能产生一些将来的维护问题。假设你决定扩展上述的类层次,从MediaFile 派生另一个叫 LocalizeMedia 的类,用这个类表示带有不同语言说明文字的媒体文件。但 LocalizeMedia 本质上还是个 MediaFile 类型的文件。因此,当用户在该类文件图标上单击右键时,文件管理器必须提供一个“播放”菜单。可惜 build()成员函数会调用失败,原因是你没有检查这种特定的文件类型。为了解决这个问题,你必须象下面这样对 build() 打补丁

 

  void menu::build(const File * pfile)

 

  {

 

  //......

 

  else if (typeid(*pfile)==typeid(LocalizedMedia))

 

  {

 

  add_option("play");

 

  }

 

  }

 

  唉,这种做法真是显得太业余了,以后每次添加新的类,毫无疑问都必须打类似的补丁。显然,这不是一个理想的解决方案。这个时候我们就要用到 dynamic_cast,这个运算符用于多态编程中保证在运行时发生正确的转换(即编译器无法验证是否发生正确的转换)。用它来确定某个对象是 MediaFile 对象还是它的派生类对象。dynamic_cast 常用于从多态编程基类指针向派生类指针的向下类型转换。它有两个参数:一个是类型名;另一个是多态对象的指针或引用。其功能是在运行时将对象强制转换为目标类型并返回布尔型结果。也就是说,如果该函数成功地并且是动态的将 *pfile 强制转换为 MediaFile,那么 pfile的动态类型是 MediaFile 或者是它的派生类。否则,pfile 则为其它的类型:

 

  void menu::build(const File * pfile)

 

  {

 

  if (dynamic_cast <MediaFile *> (pfile))

 

  {

 

  // pfile 是 MediaFile 或者是MediaFile的派生类 LocalizedMedia

 

  add_option("play");

 

  }

 

  else if (dynamic_cast <TextFile*> (pfile))

 

  {

 

  // pfile 是 TextFile 是TextFile的派生类

 

  add_option("edit");

 

  }

 

  }

 

  细细想一下,虽然使用 dynamic_cast 确实很好地解决了我们的问题,但也需要我们付出代价,那就是与 typeid 相比,dynamic_cast 不是一个常量时间的操作。为了确定是否能完成强制类型转换,dynamic_cast`必须在运行时进行一些转换细节操作。因此在使用 dynamic_cast 操作时,应该权衡对性能的影响。

RTTI的更多相关文章

  1. C 语言Struct 实现运行类型识别 RTTI

    通过RTTI,能够通过基类的指针或引用来检索其所指对象的实际类型.c++通过下面两个操作符提供RTTI. (1)typeid:返回指针或引用所指对象的实际类型.    (2)dynamic_cast: ...

  2. RTTI&colon; dynamic&lowbar;cast typeid

    dynamic_cast:将基类类型的指针向派生类指针安全转换.多用于下行转换.上行转换时,和static_cast是一样的.C++类型转换看这里.而const_cast用来修改类型的const或vo ...

  3. Java系列笔记&lpar;2&rpar; - Java RTTI和反射机制

    目录 前言 传统的RTTI 反射 反射的实现方式 反射的性能 反射与设计模式 前言 并不是所有的Class都能在编译时明确,因此在某些情况下需要在运行时再发现和确定类型信息(比如:基于构建编程,),这 ...

  4. RTTI 运行时类型识别 及异常处理

    RTTI   运行时类型识别 typeid  ------  dynamic_cast dynamic_cast 注意事项: 1.只能应用于指针和引用之间的转化 2.要转换的类型中必须包含虚函数 3. ...

  5. 【Java基础】RTTI与反射之Java

    一.引言 很多时候我们的程序可能需要在运行时识别对象和类的信息,比如多态就是基于运行时环境进行动态判断实际引用的对象.在运行时识别对象和类的信息主要有两种方式:1.RTTI,具体是Class对象,它假 ...

  6. c&plus;&plus; typeid获取类型名-rtti

    typeid操作符的作用就是获取一个表达式的类型.返回结果是const type_info&.不同编译器实现的type_info class各不相同.但c++标准保证它会实现一个name()方 ...

  7. C&plus;&plus; 中dynamic&lowbar;cast&amp&semi;lt&semi;&amp&semi;gt&semi;的使用方法小结 -判断类型-rtti

    将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理          即会作一定的判断.        对指针进行dynamic ...

  8. Java RTTI机制与反射机制

    1.1 什么是RTTI? *的定义:In computer programming, RTTI (Run-Time Type Information, or Run-Time Type Iden ...

  9. RTTI (Run-Time Type Identification,通过运行时类型识别) 转

    参考一: RTTI(Run-Time Type Identification,通过运行时类型识别)程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型.   RTTI提供了以下两个 ...

随机推荐

  1. CF 444C DZY Loves Physics(图论结论题)

    题目链接: 传送门 DZY Loves Chemistry time limit per test1 second     memory limit per test256 megabytes Des ...

  2. 【leetcode】 Search a 2D Matrix &lpar;easy&rpar;

    Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the follo ...

  3. 北京大学信息科学技术学院计算机专业课程大纲选摘--JAVA

  4. char型变量理解

    char  c = 128; printf("%d", c); 问输出是多少? 正确答案应该是-128. 如下几种情况: char c=128;printf("%u\n& ...

  5. mysql 字段编码该为utf8mb4

    alter table c_comment modify column content varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unico ...

  6. 1 &amp&semi; 167&period; Two Sum I &amp&semi; II &lpar; Input array is sorted &rpar;

    Input array is sorted: Use binary search or two pointers Unsorted: Use hash map, key = target - a[i] ...

  7. CSS3新特性汇总

    新增选择器 p:nth-child(n){color: rgba(255, 0, 0, 0.75)} 弹性盒模型 display: flex; 多列布局 column-count: 5; 媒体查询 @ ...

  8. mysql数据字段整理

    <?php header('content-type:text/html;charset=utf-8'); define('DB_HOST','127.0.01'); define('DB_US ...

  9. 开源CFD并非万金油

    今天有网友在群里讨论开发CFD软件的事情,众说纷纭,有网友提到"没有必要开发CFD软件了,直接使用开源OpenFOAM就行".但个人认为这说法还是有一些需要商榷的地方,开源软件也不 ...

  10. lua笔记二 赋值语句

    赋值是改变一个变量的值和改变表域的最基本的方法. a = "hello" .. "world" t.n = t.n + 1 Lua可以对多个变量同时赋值,变量列 ...