C++ RTTI特性:typeid/dynamic_cast及常用四种类型转换运算符

一、定义RTTI:Run Time Type Identification ,运行时类型识别:指程序能够使用基类的指针或引用来检索其所指对象的实际派生类型。
二、使用方式C++中有两个操作符提供RTTI:
(1)typeid 操作符:返回指针或引用所指对象的实际类型。
(2)dynamic_cast 操作符:将基类类型的指针或引用安全地转换为派生类型的指针和引用。
 此二操作符只为带有一个或多个虚函数的类返回动态类型信息----即在运行时执行RTTI操作符;对于其他类型则返回静态类型的信息---即在编译时计算RTTI操作符
三、详细介绍(1)typeid
头文件# include<typeinfo>
语法--两种形式:typeid (type) 、typeid (expression)即任意表达式或类型名。
常见的用途:比较两个表达式的类型,或者将表达式的类型与特定类型相比较。
typeid返回类型const type_info&;
具体定义如下:
class type_info{
public: 
	virtul ~type_info(); 
	bool operator == (const type_info&rhs)const; 
	bool operator != (const type_info&rhs)const; 
	bool before(const type_info&rhs)const; 
	const char* name()const; 
private: 
	type_info(const type_info& rhs); 
	type_info& operator=(const type_info& rhs);
}
接口说明:
operator ==和operator!=:比较操作符,返回两个类型是否为(或不为)同一类型(注:基类和派生类不为同一类型!)。
before:若在类型排序中,该类型先于rhs的类型则返回true。
name:返回类型对应的名字(具体所用的值,依赖于具体编译器)(以\0结束的字符串)。
注意type_info类的默认构造函数和复制构造函数以及赋值操作符都定义为private,故不能定义或复制type_info类型的对象程序中创建type_info对象的唯一方法是使用typeid操作符。
例子:(来源
#include <iostream>
#include <typeinfo>
using namespace std;

struct Base {};
struct Derived : Base {};
struct Poly_Base {virtual void Member(){}};
struct Poly_Derived: Poly_Base {};

int main() {
  int a;
  int * pa;
  cout << "int is: " << typeid(int).name() << endl;
  cout << "  a is: " << typeid(a).name() << endl;
  cout << " pa is: " << typeid(pa).name() << endl;
  cout << "*pa is: " << typeid(*pa).name() << endl << endl;

  Derived derived;
  Base* pbase = &derived;
  cout << "derived is: " << typeid(derived).name() << endl;
  cout << "*pbase is: " << typeid(*pbase).name() << endl;
  cout << "same type? ";
  cout << ( typeid(derived)==typeid(*pbase) ) << endl << endl;


  Poly_Derived polyderived;
  Poly_Base* ppolybase = &polyderived;
  cout << "polyderived is: " << typeid(polyderived).name() << endl;
  cout << "*ppolybase is: " << typeid(*ppolybase).name() << endl;
  cout << "same type? ";
  cout << ( typeid(polyderived)==typeid(*ppolybase) ) << endl << endl;
  return 0;
}

运行结果:(尝试了两个编译环境:g++ (GCC) 4.4.6 20120305 (Red Hat 4.4.6-4)  和 VC6.0)
G++结果:


VC6.0结果:


注:虽然 typeid(*ppolybase)返回派生类类型,但是 typeid(ppolybase)依旧是返回基类指针类型。同理,引用类似:如果r是引用,typeid(r)返回派生类类型,typeid(&r)则依旧返回基类类型。(原作者说话说一半就,为啥呢:因为Base类不包含虚函数,所以其RTTI是静态消息,编译时候已经确定,所以pbase就是base指针的类型,不会进行动态更改,而polybase包含虚函数,所以是动态RTTI,使用ppolybase指向derived类也会动态改变。)
注意到两个编译环境的运行结果不一样---即typeid在不同编译环境下返回的结果是不一致的!

为什么会这样呢?
因为:标准C++规定,type_info类的确切定义随编译器而变化,只要保证所有的实现提供以上的基本操作就行(见类定义)。即具体实现细节,各编译器厂商可自行决定。
注:在VC6.0运行时,记得把编译选项加上“/GR“ ,否则编译时会出现Warning(工程--设置--C/C++---工程选项)。因为VC6.0默认不开启RTTI。
(2)dynamic_cast
作用:将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理, 
       即会作一定的判断。 
       对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针; 
       对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用。 
  
注意:dynamic_cast在将父类cast到子类时,父类必须要有虚函数。例如在下面的代码中将CBasic类中的test函数不定义成 
       virtual时,编译器会报错:error C2683: dynamic_cast : “CBasic”不是多态类型 
  语法形式dynamic_cast<T>(v) ,将对象 v 转换为类型T的对象。 前提v 要么为指向其派生类对象的基类指针,要么为引用其派生类对象的基类对象。否则,v 返回NULL(为指针时)或抛出std::bad_cast(在头文件<typeinfo>中定义)异常(为引用类型时);而T为所期望的派生类指针类型或派生类引用类型。且,v指向的基类里必须包含虚函数,即多态类型,否则编译出错。 常用写法: I、
Poly_Derived* derivedPtr = dynamic_cast<Poly_Derived*>(ppolybase);//转换为指向Poly_Derived 型的指针,失败返回NULL;
II、
Poly_Derived& derivedRef = dynamic_cast<Poly_Derived&>(polyderived); //转换为Poly_Derived 引用,失败时抛出bad_cast异常。
具体见例子
#include <iostream>
#include <typeinfo>
using namespace std;

struct Poly_Base {virtual void Member(){}};
struct Poly_Derived: Poly_Base {};

int main() {
  Poly_Derived polyderived;
  Poly_Base* ppolybase = &polyderived;
  Poly_Base& rpolybase = polyderived;
  if(Poly_Derived* derivedPtr = dynamic_cast<Poly_Derived*>(ppolybase))//base pointer
  {
      cout<<"dynamic_cast pointer success."<<endl;
  }
  else
  {
      cout<<"dynamic_cast pointer fail!"<<endl;
  }
  try{
      const Poly_Derived& derivedRef = dynamic_cast<const Poly_Derived&>(rpolybase);
      cout<<"dynamic_cast reference success."<<endl;
  }catch(bad_cast){
      cout<<"dynamic_cast reference fail."<<endl;
  }
  cout <<"same type? ";
  cout << ( typeid(rpolybase)==typeid(*ppolybase) ) << endl;
  return 0;
}

运行结果:

再如栗子:

#include <iostream> 
using namespace std; 
  
class CBasic 
{ 
public: 
     virtual int test(){return 0;} // 一定要是 virtual 
}; 
  
class CDerived : public CBasic 
{ 
public: 
     virtual int test(){    return 1;} 
}; 
  
int main() 
{ 
     CBasic        cBasic; 
     CDerived    cDerived; 
      
     CBasic * pB1 = new CBasic; 
     CBasic * pB2 = new CDerived; 
  
     //dynamic cast failed, so pD1 is null. 
     CDerived * pD1 = dynamic_cast<CDerived * > (pB1);    
                  
     //dynamic cast succeeded, so pD2 points to  CDerived object                                         
     CDerived * pD2 = dynamic_cast<CDerived * > (pB2);    
      
     //dynamci cast failed, so throw an exception.             
//    CDerived & rD1 = dynamic_cast<CDerived &> (*pB1);    
  
//dynamic cast succeeded, so rD2 references to CDerived object. 
     CDerived & rD2 = dynamic_cast<CDerived &> (*pB2);    
  
     return 0; 
} 



参考文章:

1.  http://www.cplusplus.com/reference/typeinfo/type_info/
2.  http://en.cppreference.com/w/cpp/language/typeid
3.  http://stackoverflow.com/questions/1986418/typeid-and-typeof-in-c
4.  http://renhl252.blog.163.com/blog/static/2122100720098229281284/


除了已经出现的dynamic_cast指针之外,C++常用的其余三种类型转换运算符:

 一、const_cast:
只影响类型修饰符const,volatile
用法:const_cast<type_id>(expression)

    该运算符用来修改类型的constvolatile属性。除了const或volatile修饰之外,type_id和expression的类型是一样的。

  • 常量指针被转化成非常量指针,并且仍然指向原来的对象;
  • 常量引用被转换成非常量引用,并且仍然指向原来的对象;
  • 常量对象被转换成非常量对象
例如:
const int* pci = 0;  
int* pk = const_cast<int*>(pci); //相当于int* pk = (int*)pci;  
const A* pca = new A;  
A* pa = const_cast<A*>(pca); //相当于A* pa = (A*)pca; 

二、static_cast
  • 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。进行上行转换把派生类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的
  • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
  • 空指针转换成目标类型的空指针
  • 把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。
C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作。它拥有与C转换相同的威力与意义,以及相同的限制。例如,不能用static_cast将一个struct转型为int,或将一个double转型为pointer。
例如:
//基本类型转换  
int i=0;  
double d = static_cast<double>(i); //相当于 double d = (double)i;  
//转换继承类的对象为基类对象  
class Base{};  
class Derived : public Base{};  
Derived d;  
Base b = static_cast<Base>(d); //相当于 Base b = (Base)d; 

三、reinterpret_cast:
  • 操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型没有进行二进制转换
  • 所有映射中最危险的。
  • 最常用的用途是转换“函数指针”类型,例如:
typedef void (*FuncPtr)();
int doSomething();
FuncPtr funcPtr = reinterpret_cast<FuncPtr>(&doSomething);
  • static_cast和reinterpret_cast的区别主要在于多重继承,比如
classA { public: int m_a;};
classB { public: int m_b;};
classC : public A, public B {};

那么对于以下代码:

C c;
printf("%p,%p, %p\r\n", &c, reinterpret_cast<B*>(&c),static_cast<B*>(&c)  );


前两个的输出值是相同的,最后一个则会在原基础上偏移 4 个字节,这是因为 static_cast 计算了 父子类指针转换的偏移量 ,并将之转换到正确的地址 c里面有m_a, m_b,转换为B*指针后指到m_b ),而 reinterpret_cast 却不会做这一层转换。

再如:
//基本类型指针的类型转换  
double d=9.2;  
double* pd = &d;  
int *pi = reinterpret_cast<int*>(pd); //相当于int *pi = (int*)pd;  
//不相关的类的指针的类型转换  
class A{};  
class B{};  
A* pa = new A;  
B* pb = reinterpret_cast<B*>(pa); //相当于B* pb = (B*)pa;  
//指针转换为整数  
long l = reinterpret_cast<long>(pi); //相当于long l = (long)pi; 


  来源: < http://developer.51cto.com/art/201107/277241.htm >

 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值