分享

C 中RTTI机制剖析

 贝嘉安 2016-12-15

C++中要想在运行时获取类型信息,可没有Java中那么方便,Java中任何一个类都可以通过反射机制来获取类的基本信息(接口、父类、方法、属性、Annotation等),而且Java中还提供了一个关键字,可以在运行时判断一个类是不是另一个类的子类或者是该类的对象,但C++却没有这么多功能,C++中获得类信息只能通过RTTI机制,而且功能还是很有限的,因为C++中最终生成的代码是直接与机器相关的,而Java中会生成字节码文件,再由JVM加载运行,字节码文件中可以含有类的信息。

C++中RTTI的简单源程序示例:

  1. class A{  
  2. private:  
  3.     int a;  
  4. };  
  5. class B{  
  6. public:   
  7.     //加一个虚函数  
  8.     virtual void f(){}  
  9. };  
  10. class C:public B  
  11. {  
  12. public :  
  13.     void f(){};  
  14. };  
  15. class D:public A  
  16. {  
  17. public:  
  18.     void f(){}  
  19. };  
  20. int main()  
  21. {  
  22.     int a=2;  
  23.     //打印出int  
  24.     cout<<typeid(a).name()<
  25.     A objA;  
  26.     //打印出class A  
  27.     cout<<typeid(objA).name()<
  28.     B objB;  
  29.     //打印出class B  
  30.     cout<<typeid(objB).name()<
  31.     C objC;  
  32.     //打印出class C  
  33.     cout<<typeid(objC).name()<
  34.     /* 
  35.     //以下是多态在VC 6.0编译器不支持,但是在GCC以及微软更高版本的编译器却都是 
  36.     //支持的,且是在运行时候来确定类型的,而不是在编译器,会打印出class c 
  37.     B *ptr=new C(); 
  38.     cout< 
  39.     */  
  40.     A *ptr=new D();  
  41.     //打印出class A而不是class D  
  42.     cout<<typeid(*ptr).name()<
  43.     return 0;  
  44. }  

要想理解上述代码:我们需要明白以下几个事实

1:typeid是一个关键字

2:typeid的结果有时候在编译期确定有时间会在执行期确定

3:typeid运行时,会将判断的结果存储在一个consttypeinfo&对象中

4:不同的编译器对typeid运算的结果差异很大,例如在VC 6.0与G++编译器中,G++编译器支持运行时动态确定类型,而VC 6.0则不支持。

1:typeid是一个关键字,可以在任意一本C++入门书中看到,typeid是一个关键字,像Sizeof一样,要是函数的话,函数传参你有见过这样的吗typeid(int),直接传int,而不是传一个整型值的,我是没见过:)

2:看看上述的程序,你会发现上述程序中除了多态的那一部份(在VC 6.0中是无法编译通过的),其他的均是在编译期运行,多态的会在执行期去运行,为了更具说服务力,看看下面的代码,是上面程序的部分汇编代码:

  1. 30:       //打印出int  
  2. 31:       const type_info &t=typeid(a);//从下面的汇编代码中可以看出类型在编译期就已经确定了  
  3. 004011C4   mov         dword ptr [ebp-14h],offset int `RTTI Type Descriptor' (00441e08)  
  4. 32:       cout<
  5. 004011CB   push        offset @ILT+35(std::endl) (00401028)  
  6. 004011D0   mov         ecx,dword ptr [ebp-14h]  
从上面的程序,可以看出对于不是多态类型的,直接在编译器就解决了类型的确定,这样有利于减少程序的运行时间

对于多态类型(看看上面程序中注释掉部分代码在VS 2010中的反汇编代码):

  1.     cout<<typeid(*ptr1).name()<
  2. 00A451B4  mov         esi,esp    
  3. 00A451B6  mov         eax,dword ptr ds:[00A5132Ch]    
  4. 00A451BB  push        eax    
  5. 00A451BC  mov         edi,esp    
  6. 00A451BE  push        0A5027Ch    
  7. 00A451C3  mov         ecx,dword ptr [ptr1]    
  8. 00A451C6  push        ecx    
  9. ;可以看出的是在这里调用了__RTtypeid函数,运行的时候来确定指针所指对象的真实类型  
  10. 00A451C7  call        ___RTtypeid (0A414BAh)    
  11. 00A451CC  add         esp,4    
  12.     cout<<typeid(*ptr1).name()<

从上面的汇编代码中可以看出的是对于类中有虚函数(多态)会在运行时决定类的类型

看看RTtypeid的实现吧

  1. template<typename T>  
  2. const TypeDescriptor *__RTtypeid(const T *ptr)  
  3. {  
  4.     if (!ptr) throw new std::bad_typeid('Attempted a typeid of NULL pointer!');  
  5.     //获取指针把指对象的描述符,这里说明了一个问题,是对于多态类,里面会有一个指针指向这个描述符  
  6.     const _s_RTTICompleteObjectLocator *pCompleteLocator=GetCompleteObjectLocator(ptr);  
  7.     //获取其描述信息  
  8.     TypeDescriptor *pTypeDescriptor=pCompleteLocator->pTypeDescriptor;  
  9.     //如果未获取到,或者指针为空时,执行下面的逻辑  
  10.     if (!pTypeDescriptor) {  
  11.         throw std::__non_rtti_object('Bad read pointer - no RTTI data!');  
  12.     }  
  13.     return pTypeDescriptor;  
  14. }  
这里的实现还是挺简单的,不是嘛:),其真实的实现原理是,每一个函数数类均有一个虚函数表,编译器会将类的vftable(包括它自己的和从基类继承的)的第一个函数指针前面插入一个指向_s_RTTICompleteObjectLocator结构的指针(描述类信息的指针),这个结构中会存放该类的TypeDescriptor(上面的GetCompleteObjectLocator函数就是用来从vftable获得s_RTTICompleteObjectLocator结构的),因此,即使你将派生类的指针赋给基类的指针,你仍然可以利用上面的算法得到派生类的类型.

typeid关键字将类型信息存放在一个const type_info类中,看看这个类的具体源代码吧

  1. class type_info {  
  2. public:  
  3.         //析构函数  
  4.     _CRTIMP virtual ~type_info();  
  5.     //重载的==操作符  
  6.     _CRTIMP int operator==(const type_info& rhs) const;  
  7.     //重载的!=操作符  
  8.     _CRTIMP int operator!=(const type_info& rhs) const;  
  9.     _CRTIMP int before(const type_info& rhs) const;//用于type_info对象之间的排序算法  
  10.     //返回类的名字  
  11.     _CRTIMP const char* name() const;  
  12.     _CRTIMP const char* raw_name() const;//返回类名称的编码字符串  
  13. private:  
  14.     //各种存储数据成员  
  15.     void *_m_data;  
  16.     char _m_d_name[1];  
  17.     //将拷贝构造函数与赋值构造函数设为了私有  
  18.     type_info(const type_info& rhs);  
  19.     type_info& operator=(const type_info& rhs);  
  20. };  

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多