浅议 Dynamic_cast 和 RTTI

转载:https://www.cnblogs.com/zhyg6516/archive/2011/03/07/1971898.html

浅议 Dynamic_cast 和 RTTI

写这篇博文的目的是,记录学习过程。

对于问题要较真,在解决这个问题中会学到很多,远远超过自己期望,锻炼思维,享受这个过程。

问题: Static_cast 与 Dynamic_cast的区别

来自书本上的解释:

  用 static_cast<type-id > ( expression )  

1. static_cast(expression) The static_cast<>() is used to cast between the integer types. 'e.g.' char->long, int->short etc.

   用来数值之间的转化。

2.  可以在相关指针之间转换,指针在void * 之间转换,还可以在基类和派生类之间转换。 这些转换是在编译的时候就确定下来转换(无非就是根据继承关系,偏移指针而已),但是这需要自己保证安全。

   比如

#include <iostream>
using namespace std;

class Base 
{
public:
    virtual void f() {cout<<"Base::f()"<<endl;}

};

class Derive: public Base
{
public:
    virtual  void f() {cout<<"Derive::f()"<<endl;}
    virtual  void f2() {cout<<"Derive::f1()"<<endl;}

};


int main()
{

    Base *pbase1  = new Derive();
    Derive* pderive1 = static_cast<Derive *>(pbase1);
    pderive1->f(); // Derive::f()
    
    Base* pbase2 = new Base();
    Derive * pderive2 = static_cast<Derive *>(pbase2);
    pderive2->f();  // Base::f()
    pderive2->f2(); // throw exception "Access violation reading"

    delete pbase1;
    delete pbase2;

}
 

虽然 由 pbase2 转化到 pderive2 ,编译器编译正确。 但是当调用 pderive2->f(); 应该不是希望的; 调用pderive2->f2() 因为基类本身就没有这个函数,说以运行时出错,抛出异常。

所以说static_cast 是编译时确定下来,需要自己确保转换类型安全,否则运行时会抛出异常.

 注意static_cast 不能直接在没有继承关系的对象指针之间进行转换。在Com 里面实现不同接口的同个对象,其也不能再接口之间转换(更何况是动态的),所以COM提供一个query 借口。

用法:dynamic_cast < type-id > ( expression)

是专门用于具有继承关系的类之间转换的,尤其是向下类型转换,是安全的。

#include <iostream>
using namespace std;

class Base
{
public:
virtual void f() {cout<<"Base::f()"<<endl;}

};

class Derive: public Base
{
public:
virtual void f() {cout<<"Derive::f()"<<endl;}
virtual void f2() {cout<<"Derive::f1()"<<endl;}

};

int main()
{

Base *pbase1 = new Derive();
Derive* pderive1 = dynamic_cast<Derive *>(pbase1); //down-cast
pderive1->f(); // Derive::f()

Base* pbase2 = new Base();
Derive * pderive2 = dynamic_cast<Derive *>(pbase2); //up-cast

if ( pderive2) // NULL
{
pderive2->f();
pderive2->f2();
}

delete pbase1;
delete pbase2;

}
 

dynamic_cast 如何保证转换是安全的? 如何知道具体该类的具体类型 以及 继承关系呢?

引入RTTI,其存储着类运行的相关信息, 比如类的名字,以及类的基类。下面就介绍RTTI。

2. RTTI (Run Time Type info)

  这个神奇的东西用于存储类的相关信息,用于在运行时识别类对象的信息。C++ 里面只记录的类的名字和类的继承关系链。使得编译成二进制的代码,对象可以知道自己的名字(ASCII),以及在继承链中的位置。

  C++ 里面提供 一个关键字 typeid , 一个数据类型 typeinfo,以及对应的头文件 typeinfo.h

#include <iostream>
#include <typeinfo>
 using namespace std;

 class Base 
{
 public:
    virtual void f() {}  // it must need the virtual table
};


 class Derive: public Base
{

};


 class Derive2: public Base
{
 
};

 void f(Base* pbase)
{
  const type_info& typeinfo = typeid(pbase);
  cout << typeinfo.name()<<endl;


  if(NULL != dynamic_cast<Derive*>(pbase))
  {
      cout<<"type: Derive"<<endl;
  }
  else if(NULL != dynamic_cast<Derive2*>(pbase))
  {
      cout<<"type: Derive2"<<endl;
  }
  else
  {
       //ASSERT(0)
   }
}


 int main()
{
    Base *pbase1  = new Derive();
    f(pbase1);

    Base* pbase = new Derive2();
    f(pbase);
}
out put:
1 class Base *
2 type: Derive
3  class Base *
4 type: Derive2

可见 Dynamic 是运行时确定的,是安全的。 那么

1. RTTI 的信息如何和对象绑定在一起?什么时候绑定的?

2. 为什么dynam_cast 必须要求转换的类型之间要有虚函数?否则编译通不过。

下面来回答这个问题。

3.RTTI 如何与对象绑定

google,找资料。 下面的图来自于 “Inside C++ Model”, RTTI 的info 是如何和对象之间的关系:

class Point 
{ 

public: 

   Point( float xval ); 

   virtual ~Point(); 

   float x() const; 

   static int PointCount(); 

protected: 

   virtual ostream& print( ostream &os ) const; 

   float _x; 

   static int _point_count; 

};
其内存中模型:

明显RTTI info 存在于虚表的第一项。第二个问题就可以回答,因为RTTI 依赖于虚表,所以用dynamic_cast 对应的类一定要有虚函数。

下面在VC中验证一下,

在VC中,我们知道虚指针指向虚表,对应的虚表第一项就是第一个虚函数。如果我们认为虚函数构成虚表,那么就可以认为RTTI info 就走虚表的紧邻上面。

下面验证:

1. 在VC 中查看RTTI中类名字

从上面图表可见,RTTI 对应的内容是空的。那么VC的实现和 书中的模型不一致吗?难道RTTI不在虚表的上面吗 ?接着有了下面的验证:

2. 把虚表上面指向RTTI info 的地址,给设置为0, 那么typeid 还可以工作吗? Dynamic_cast 还可以工作吗?如果还可以工作,则说明这个地址指向的数据无关。

  如果将虚表上的RTTI的指针置空,dynamic_cast 就不能运行,抛出异常“std:: __non_rtti_object” . 那说明这个地址,还是与RTTI有关。 那问题出在哪里?

尝试在google 里面搜索,但是未果。 那么Dynamic_cast 的依赖于 RTTI的信息,那么Dynamic_cast的实现着手看看. 查看一下 他的汇编代码。 于是有了下面的实验。

3. RTTI 在VC里面如何实现的。

  将上面的代码以汇编形式输出,查看。

:     Derive * pderive = dynamic_cast<Derive*>(pbase);

    push    0
    push    OFFSET ??_R0?AVDerive@@@8
    push    OFFSET ??_R0?AVBase@@@8
    push    0
    mov    eax, DWORD PTR _pbase$[ebp]
    push    eax
    call    ___RTDynamicCast
    add    esp, 20                    ; 00000014H
    mov    DWORD PTR _pderive$[ebp], eax
发现 dynamic_cast的实现依赖于 对象本身,以及 ??_R0?AVDerive@@@8 和 ??_R0?AVBase@@@8 .  于是继续查看代码

 

1 ; Listing generated by Microsoft (R) Optimizing Compiler Version 14.00.50727.762 
 
    TITLE    c:\Documents and Settings\zhangroc\Desktop\TW\Test\static_cast\static_cast.cpp
    .686P
    .XMM
    include listing.inc
    .model    flat

INCLUDELIB MSVCRTD
INCLUDELIB OLDNAMES

PUBLIC    ??_C@_0P@GHFPNOJB@bad?5allocation?$AA@        ; `string'
 _DATA    SEGMENT
__bad_alloc_Message DD FLAT:??_C@_0P@GHFPNOJB@bad?5allocation?$AA@
_DATA    ENDS
 ;    COMDAT ??_C@_0P@GHFPNOJB@bad?5allocation?$AA@
 CONST    SEGMENT
??_C@_0P@GHFPNOJB@bad?5allocation?$AA@ DB 'bad allocation', 00H ; `string'
 CONST    ENDS
PUBLIC    ??_R0?AVBase@@@8                ; Base `RTTI Type Descriptor'
 PUBLIC    ??_R0?AVDerive@@@8                ; Derive `RTTI Type Descriptor'
 PUBLIC    ??0Derive@@QAE@XZ                ; Derive::Derive
 PUBLIC    _main
EXTRN    ___RTDynamicCast:PROC
EXTRN    ??2@YAPAXI@Z:PROC                ; operator new
 EXTRN    __RTC_CheckEsp:PROC
EXTRN    __RTC_Shutdown:PROC
EXTRN    __RTC_InitBase:PROC
EXTRN    ??_7type_info@@6B@:QWORD            ; type_info::`vftable'
;    COMDAT ??_R0?AVBase@@@8
; File c:\documents and settings\zhangroc\desktop\tw\test\static_cast\static_cast.cpp
 _DATA    SEGMENT
??_R0?AVBase@@@8 DD FLAT:??_7type_info@@6B@        ; Base `RTTI Type Descriptor'
     DD    00H
    DB    '.?AVBase@@', 00H
_DATA    ENDS
 ;    COMDAT ??_R0?AVDerive@@@8
_DATA    SEGMENT
??_R0?AVDerive@@@8 DD FLAT:??_7type_info@@6B@        ; Derive `RTTI Type Descriptor'
    DD    00H
    DB    '.?AVDerive@@', 00H
_DATA    ENDS
;    COMDAT rtc$TMZ
rtc$TMZ    SEGMENT
__RTC_Shutdown.rtc$TMZ DD FLAT:__RTC_Shutdown
rtc$TMZ    ENDS
;    COMDAT rtc$IMZ
rtc$IMZ    SEGMENT
__RTC_InitBase.rtc$IMZ DD FLAT:__RTC_InitBase
; Function compile flags: /Odtp /RTCsu /ZI
rtc$IMZ    ENDS
;    COMDAT _main
_TEXT    SEGMENT
tv69 = -256                        ; size = 4
$T3121 = -248                        ; size = 4
_pderive$ = -44                        ; size = 4
_rtti$ = -32                        ; size = 4
_ptable$ = -20                        ; size = 4
_pbase$ = -8                        ; size = 4
_main    PROC                        ; COMDAT

; 19   : {

    push    ebp
    mov    ebp, esp
    sub    esp, 256                ; 00000100H
    push    ebx
    push    esi
    push    edi
    lea    edi, DWORD PTR [ebp-256]
    mov    ecx, 64                    ; 00000040H
    mov    eax, -858993460                ; ccccccccH
    rep stosd

; 20   :     Base *pbase  = new Derive();

    push    4
    call    ??2@YAPAXI@Z                ; operator new
    add    esp, 4
    mov    DWORD PTR $T3121[ebp], eax
    cmp    DWORD PTR $T3121[ebp], 0
    je    SHORT $LN3@main
    mov    ecx, DWORD PTR $T3121[ebp]
    call    ??0Derive@@QAE@XZ
    mov    DWORD PTR tv69[ebp], eax
    jmp    SHORT $LN4@main
$LN3@main:
    mov    DWORD PTR tv69[ebp], 0
$LN4@main:
    mov    eax, DWORD PTR tv69[ebp]
    mov    DWORD PTR _pbase$[ebp], eax

; 21   :     int *ptable = (int*)(*(int*)pbase);

    mov    eax, DWORD PTR _pbase$[ebp]
    mov    ecx, DWORD PTR [eax]
    mov    DWORD PTR _ptable$[ebp], ecx

; 22   :     int *rtti = ptable -1;

    mov    eax, DWORD PTR _ptable$[ebp]
    sub    eax, 4
    mov    DWORD PTR _rtti$[ebp], eax

; 23   :     
; 24   :     Derive * pderive = dynamic_cast<Derive*>(pbase);

    push    0
    push    OFFSET ??_R0?AVDerive@@@8
    push    OFFSET ??_R0?AVBase@@@8
    push    0
    mov    eax, DWORD PTR _pbase$[ebp]
    push    eax
    call    ___RTDynamicCast
    add    esp, 20                    ; 00000014H
    mov    DWORD PTR _pderive$[ebp], eax

; 25   :    
; 26   : }

    xor    eax, eax
    pop    edi
    pop    esi
    pop    ebx
    add    esp, 256                ; 00000100H
    cmp    ebp, esp
    call    __RTC_CheckEsp
    mov    esp, ebp
    pop    ebp
    ret    0
_main    ENDP
_TEXT    ENDS
PUBLIC    ??_7Derive@@6B@                    ; Derive::`vftable'
PUBLIC    ??0Base@@QAE@XZ                    ; Base::Base
PUBLIC    ??_R4Derive@@6B@                ; Derive::`RTTI Complete Object Locator'
PUBLIC    ??_R3Derive@@8                    ; Derive::`RTTI Class Hierarchy Descriptor'
PUBLIC    ??_R2Derive@@8                    ; Derive::`RTTI Base Class Array'
PUBLIC    ??_R1A@?0A@EA@Derive@@8                ; Derive::`RTTI Base Class Descriptor at (0,-1,0,64)'
PUBLIC    ??_R1A@?0A@EA@Base@@8                ; Base::`RTTI Base Class Descriptor at (0,-1,0,64)'
PUBLIC    ??_R3Base@@8                    ; Base::`RTTI Class Hierarchy Descriptor'
PUBLIC    ??_R2Base@@8                    ; Base::`RTTI Base Class Array'
PUBLIC    ?f@Base@@UAEXXZ                    ; Base::f
;    COMDAT ??_R2Base@@8
rdata$r    SEGMENT
??_R2Base@@8 DD    FLAT:??_R1A@?0A@EA@Base@@8        ; Base::`RTTI Base Class Array'
rdata$r    ENDS
;    COMDAT ??_R3Base@@8
rdata$r    SEGMENT
??_R3Base@@8 DD    00H                    ; Base::`RTTI Class Hierarchy Descriptor'
    DD    00H
    DD    01H
    DD    FLAT:??_R2Base@@8
rdata$r    ENDS
;    COMDAT ??_R1A@?0A@EA@Base@@8
rdata$r    SEGMENT
??_R1A@?0A@EA@Base@@8 DD FLAT:??_R0?AVBase@@@8        ; Base::`RTTI Base Class Descriptor at (0,-1,0,64)'
    DD    00H
    DD    00H
    DD    0ffffffffH
    DD    00H
    DD    040H
    DD    FLAT:??_R3Base@@8
rdata$r    ENDS
;    COMDAT ??_R1A@?0A@EA@Derive@@8
rdata$r    SEGMENT
??_R1A@?0A@EA@Derive@@8 DD FLAT:??_R0?AVDerive@@@8    ; Derive::`RTTI Base Class Descriptor at (0,-1,0,64)'
    DD    01H
    DD    00H
    DD    0ffffffffH
    DD    00H
    DD    040H
    DD    FLAT:??_R3Derive@@8
rdata$r    ENDS
;    COMDAT ??_R2Derive@@8
rdata$r    SEGMENT
??_R2Derive@@8 DD FLAT:??_R1A@?0A@EA@Derive@@8        ; Derive::`RTTI Base Class Array'
    DD    FLAT:??_R1A@?0A@EA@Base@@8
rdata$r    ENDS
;    COMDAT ??_R3Derive@@8
rdata$r    SEGMENT
??_R3Derive@@8 DD 00H                    ; Derive::`RTTI Class Hierarchy Descriptor'
    DD    00H
    DD    02H
    DD    FLAT:??_R2Derive@@8
rdata$r    ENDS
;    COMDAT ??_R4Derive@@6B@
rdata$r    SEGMENT
??_R4Derive@@6B@ DD 00H                    ; Derive::`RTTI Complete Object Locator'
    DD    00H
    DD    00H
    DD    FLAT:??_R0?AVDerive@@@8
    DD    FLAT:??_R3Derive@@8
rdata$r    ENDS
;    COMDAT ??_7Derive@@6B@
CONST    SEGMENT
??_7Derive@@6B@ DD FLAT:??_R4Derive@@6B@        ; Derive::`vftable'
    DD    FLAT:?f@Base@@UAEXXZ
; Function compile flags: /Odtp /RTCsu /ZI
CONST    ENDS
;    COMDAT ??0Derive@@QAE@XZ
_TEXT    SEGMENT
_this$ = -8                        ; size = 4
??0Derive@@QAE@XZ PROC                    ; Derive::Derive, COMDAT
; _this$ = ecx
    push    ebp
    mov    ebp, esp
    sub    esp, 204                ; 000000ccH
    push    ebx
    push    esi
    push    edi
    push    ecx
    lea    edi, DWORD PTR [ebp-204]
    mov    ecx, 51                    ; 00000033H
    mov    eax, -858993460                ; ccccccccH
    rep stosd
    pop    ecx
    mov    DWORD PTR _this$[ebp], ecx
    mov    ecx, DWORD PTR _this$[ebp]
    call    ??0Base@@QAE@XZ
    mov    eax, DWORD PTR _this$[ebp]
    mov    DWORD PTR [eax], OFFSET ??_7Derive@@6B@
    mov    eax, DWORD PTR _this$[ebp]
    pop    edi
    pop    esi
    pop    ebx
    add    esp, 204                ; 000000ccH
    cmp    ebp, esp
    call    __RTC_CheckEsp
    mov    esp, ebp
    pop    ebp
    ret    0
??0Derive@@QAE@XZ ENDP                    ; Derive::Derive
; Function compile flags: /Odtp /RTCsu /ZI
_TEXT    ENDS
;    COMDAT ?f@Base@@UAEXXZ
_TEXT    SEGMENT
_this$ = -8                        ; size = 4
?f@Base@@UAEXXZ PROC                    ; Base::f, COMDAT
; _this$ = ecx

; 8    :     virtual void f() {}

    push    ebp
    mov    ebp, esp
    sub    esp, 204                ; 000000ccH
    push    ebx
    push    esi
    push    edi
    push    ecx
    lea    edi, DWORD PTR [ebp-204]
    mov    ecx, 51                    ; 00000033H
    mov    eax, -858993460                ; ccccccccH
    rep stosd
    pop    ecx
    mov    DWORD PTR _this$[ebp], ecx
    pop    edi
    pop    esi
    pop    ebx
    mov    esp, ebp
    pop    ebp
    ret    0
?f@Base@@UAEXXZ ENDP                    ; Base::f
_TEXT    ENDS
PUBLIC    ??_7Base@@6B@                    ; Base::`vftable'
PUBLIC    ??_R4Base@@6B@                    ; Base::`RTTI Complete Object Locator'
;    COMDAT ??_R4Base@@6B@
rdata$r    SEGMENT
??_R4Base@@6B@ DD 00H                    ; Base::`RTTI Complete Object Locator'
    DD    00H
    DD    00H
    DD    FLAT:??_R0?AVBase@@@8
    DD    FLAT:??_R3Base@@8
rdata$r    ENDS
;    COMDAT ??_7Base@@6B@
CONST    SEGMENT
??_7Base@@6B@ DD FLAT:??_R4Base@@6B@            ; Base::`vftable'
    DD    FLAT:?f@Base@@UAEXXZ
; Function compile flags: /Odtp /RTCsu /ZI
CONST    ENDS
;    COMDAT ??0Base@@QAE@XZ
_TEXT    SEGMENT
_this$ = -8                        ; size = 4
??0Base@@QAE@XZ PROC                    ; Base::Base, COMDAT
; _this$ = ecx
    push    ebp
    mov    ebp, esp
    sub    esp, 204                ; 000000ccH
    push    ebx
    push    esi
    push    edi
    push    ecx
    lea    edi, DWORD PTR [ebp-204]
    mov    ecx, 51                    ; 00000033H
    mov    eax, -858993460                ; ccccccccH
    rep stosd
    pop    ecx
    mov    DWORD PTR _this$[ebp], ecx
    mov    eax, DWORD PTR _this$[ebp]
    mov    DWORD PTR [eax], OFFSET ??_7Base@@6B@
    mov    eax, DWORD PTR _this$[ebp]
    pop    edi
    pop    esi
    pop    ebx
    mov    esp, ebp
    pop    ebp
    ret    0
??0Base@@QAE@XZ ENDP                    ; Base::Base
_TEXT    ENDS
END







原来虚表上面指向是一个 Derive::`RTTI Complete Object Locator 。 用google 搜索下面的该关键字,有了下面的文章
http://www.openrce.org/articles/full_view/23
和该图:
原来虚表上面指向是一个 Derive::`RTTI Complete Object Locator 。 用google 搜索下面的该关键字,有了下面的文章
http://www.openrce.org/articles/full_view/23
和该图:


谜底揭晓: 原来虚表上面的地址是指向一个结构 Derive::`RTTI Complete Object Locator , 这个结构指向该类的名字,和其对象继承链。

这就回答了第一个问题,RTTI info 如何和对象绑定的? 在对象创建的时候,调用构造函时候,创建虚表以及RTTI info,这样dynamic cast 就可以去访问RTTI,从而保证安全。

同样有个一问题,那就是RTTI 效率底下,试下如果一个类其继承多层,而且有多继承,那么查找链就相当遍历一个链表。

4. 实现一个代码用来从RTTI中读取类名字

#include "iostream"
  #include "string" 
#include <typeinfo>
  using namespace std;


  class Base
  {
  public:
      virtual void f() { }
  };

  class Derive : public Base
  {
  };

  typedef unsigned long DWORD;

  struct TypeDescriptor
  {
      DWORD ptrToVTable;
      DWORD spare;
      char name[ ];
  };
   struct RTTICompleteObjectLocator
  
  {
  
      DWORD signature; //always zero ?
  
      DWORD offset;    //offset of this vtable in the complete class
  
      DWORD cdOffset;  //constructor displacement offset
  
      struct TypeDescriptor* pTypeDescriptor; //TypeDescriptor of the complete class
  
      int * ptr;
      //struct RTTIClassHierarchyDescriptor* pClassDescriptor; //describes inheritance hierarchy
 
  };
  

int main()
{
    
    Base *pderive = new Derive();
    
    int **ptr = (int **)(&pderive);

    int *ptable = (int *)(*(int *)(*ptr)); 

    int * rtti = ptable -1;
  
    RTTICompleteObjectLocator * RIIT_locator =   (RTTICompleteObjectLocator *)( *(int*)rtti);

    cout<<RIIT_locator->pTypeDescriptor->name<<endl;

}
Out put:

.?AVDerive@@

当然可以根据RTTI的信息,可以遍历其继承关系图。留作一个练习,可以尝试一下。

总结:

static_cast 用于数值类型之间的转换,也可以用于指针之间的转换,编译的已经确定好,效率高,但须要自己保证其安全性。

dynamic_cast 用于有继承关系的类之间转换,是基于RTTI数据信息的,运行时检测,安全,但是效率低。

Refernce:

RTTI intoduction:

1. http://www.rcs.hu/Articles/RTTI_Part1.htm [介绍RTTI 的应用,需要的借口,以及一个实现]

2. http://www.codeproject.com/KB/cpp/dynamic_cpp.aspx

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值