• static_cast而后RTT1


    问题: 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>
    usingnamespace std;

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

    };

    class Derive: public Base
    {
    public:
    virtualvoid f() {cout<<"Derive::f()"<<endl;}
    virtualvoid 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>
    usingnamespace std;

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

    };

    class Derive: public Base
    {
    public:
    virtualvoid f() {cout<<"Derive::f()"<<endl;}
    virtualvoid 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

    1#include <iostream>
    2#include <typeinfo>
    3 usingnamespace std;
    4
    5 class Base
    6{
    7 public:
    8virtualvoid f() {} // it must need the virtual table
    9};
    10
    11
    12 class Derive: public Base
    13{
    14
    15};
    16
    17
    18 class Derive2: public Base
    19{
    20
    21};
    22
    23 void f(Base* pbase)
    24{
    25const type_info& typeinfo = typeid(pbase);
    26cout << typeinfo.name()<<endl;
    27
    28
    29if(NULL != dynamic_cast<Derive*>(pbase))
    30{
    31cout<<"type: Derive"<<endl;
    32}
    33elseif(NULL != dynamic_cast<Derive2*>(pbase))
    34{
    35cout<<"type: Derive2"<<endl;
    36}
    37else
    38{
    39//ASSERT(0)
    40  }
    41}
    42
    43
    44 int main()
    45{
    46Base *pbase1 =new Derive();
    47f(pbase1);
    48
    49Base* pbase =new Derive2();
    50f(pbase);
    51}

    out put:

    1class Base *
    2type: Derive
    3 class Base *
    4type: 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;

    staticint PointCount();

    protected:

    virtual ostream& print( ostream &os ) const;

    float _x;

    staticint _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里面如何实现的。

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

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

    push0
    push OFFSET ??_R0?AVDerive@@@8
    push OFFSET ??_R0?AVBase@@@8
    push0
    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 .  于是继续查看代码

    <擅自略去汇编,有兴趣者看原文>
    原来虚表上面指向是一个 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中读取类名字

    1#include "iostream"
    2#include "string"
    3#include <typeinfo>
    4usingnamespace std;
    5
    6
    7class Base
    8{
    9public:
    10virtualvoid f() { }
    11};
    12
    13class Derive : public Base
    14{
    15};
    16
    17typedef unsigned long DWORD;
    18
    19struct TypeDescriptor
    20{
    21DWORD ptrToVTable;
    22DWORD spare;
    23char name[ ];
    24};
    25struct RTTICompleteObjectLocator
    26
    27{
    28
    29DWORD signature; //always zero ?
    30
    31DWORD offset; //offset of this vtable in the complete class
    32
    33DWORD cdOffset; //constructor displacement offset
    34
    35struct TypeDescriptor* pTypeDescriptor; //TypeDescriptor of the complete class
    36
    37int* ptr;
    38//struct RTTIClassHierarchyDescriptor* pClassDescriptor; //describes inheritance hierarchy
    39
    40};
    41
    42
    43int main()
    44{
    45
    46Base *pderive =new Derive();
    47
    48int**ptr = (int**)(&pderive);
    49
    50int*ptable = (int*)(*(int*)(*ptr));
    51
    52int* rtti = ptable -1;
    53
    54RTTICompleteObjectLocator * RIIT_locator = (RTTICompleteObjectLocator *)( *(int*)rtti);
    55
    56cout<<RIIT_locator->pTypeDescriptor->name<<endl;
    57
    58}

    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

  • 相关阅读:
    枚举
    log4j 简单应用
    [luogu4728 HNOI2009] 双递增序列 (dp)
    [luogu3760 TJOI2017] 异或和(树状数组)
    [luogu1485 HNOI2009] 有趣的数列 (组合数学 卡特兰数)
    [luogu4054 JSOI2009] 计数问题(2D BIT)
    [luogu2594 ZJOI2009]染色游戏(博弈论)
    [luogu2591 ZJOI2009] 函数
    [luogu2148 SDOI2009] E&D (博弈论)
    [luogu2154 SDOI2009] 虔诚的墓主人(树状数组+组合数)
  • 原文地址:https://www.cnblogs.com/zhanglanyun/p/2484994.html
Copyright © 2020-2023  润新知