C++的RTTI和cast运算符如何使用

2023-06-12 16:38:40 rtti 运算符 如何使用

本文小编为大家详细介绍“C++的RTTI和cast运算符如何使用”,内容详细,步骤清晰,细节处理妥当,希望这篇“C++的RTTI和cast运算符如何使用”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

    1. RTTI

    RTTI是运行阶段类型识别(Running Type Identificarion)的简称。

    如何知道指针指向的是哪种对象?

    这是个很常见的问题,由于我们允许使用基类指针指向派生类,所以基类指针指向的对象可能是基类对象,也可能是派生类对象。但是我们需要知道对象种类,因为我们需要使用正确的类方法。

    RTTI能解决上述问题。

    1.1 dynamic_cast运算符

    dynamic_cast
    是最常用的RTTI组件,它不能回答"指针指向的是哪类对象",但是它能回答"是否可以安全的将对象的地址赋给特定类型指针"。

    什么是安全?

    例如:A是基类,B是A的派生类,C是B的派生类。那么将BC对象的地址赋给A指针是安全的,而反向就是不安全的。因为公有继承满足

    is-a
    关系,指针和引用进行向上转换是安全的,向下转换就是不安全的。

    dynamic_cast
    的用法是:

    dynamic_cast<Type*>(ptr)
    dynamic_cast<Type&>(ref)

    可以看到的是,

    dynamic_cast
    是一种类型转换符,它支持指针间的转换,他将
    ptr
    的类型转换成
    Type*
    ,如果这种转换是安全的,那会返回转换后的指针;如果不安全那就会返回空指针。对于引用的话,他将
    ref
    的类型转换成
    Type&
    ,如果这种转换是安全的,那就返回转换后的引用;如果不安全那就会引发
    bad_cast
    异常。

    总之,

    dynamic_cast
    类型转换运算符比C风格的类型转换要安全的多,而且它能够判断转换是否安全。

    //RTTI1.cpp
    #include<iostream>
    #include<cstdlib>
    #include<ctime>
    using namespace std;
    class Grand
    {
    protected:
        int hold;
    public:
        Grand(int h=0):hold(h){};
        virtual void Speak() const {cout<<"I am Grand class!
    ";}
    };
    class Superb:public Grand
    {
    public:
        Superb(int h=0):Grand(h){}
        virtual void Speak() const {cout<<"I am a superb class!
    ";}
        virtual void Say() const {cout<<"the value: "<<Grand::hold<<endl;}
    };
    class Magnificent:public Superb
    {
    private:
        char ch;
    public:
        Magnificent(int h=0,char c='A'):Superb(h),ch(c){}
        virtual void Speak() const{cout<<"I am a Magnificent class!
    ";}
        virtual void Say() const{cout<<"the character: "<<ch<<endl<<"the value: "<<Grand::hold<<endl;}
    };
    Grand *GetOne();
    int main()
    {
        srand(time(0));
        Grand *pg;
        Superb *ps;
        for(int i=0;i<5;i++)
        {
            pg=GetOne();
            pg->Speak();
            if(ps=dynamic_cast<Superb*>(pg))
            {
                ps->Say();
            }
        }
        delete pg;
        return 0;
    }
    Grand * GetOne()
    {
        Grand *p;
        switch (rand()%3)
        {
            case 0:
                p=new Grand(rand()%100);
                break;
            case 1:
                p=new Superb(rand()%100);
                break;
            case 2:
                p=new Magnificent(rand()%100,'A'+rand()%26);
                break;
        }
        return p;
    }

    I am a Magnificent class!
    the character: I
    the value: 74
    I am a superb class!
    the value: 50
    I am Grand class!
    I am Grand class!
    I am a Magnificent class!
    the character: V
    the value: 99

    上面这个例子说明了重要的一点:尽可能使用虚函数,而只在必要时使用RTTI。

    如果将

    dynamic_cast
    用于引用,当请求不安全的时候,会引发
    bad_cast
    异常,这种异常时从
    exception
    类派生而来的,它在头文件
    typeinfo
    中定义。

    则我们可以使用如下方式处理异常:

    #include<typeinfo>
    ...
    try{
        Superb & rs= dynamic_cast<Superb &>(rg);
        ....
    }
    catch(bad_cast &)
    {
        ...
    };

    1.2 typeid运算符

    type_info
    类是在头文件
    typeinfo
    中定义的一个类。
    type_info
    类重载了
    ==
    !=
    运算符。

    typeid
    是一个运算符,它返回一个对
    type_info
    的引用,它可以接受2种参数:类名和对象。
    typeid
    是真正回答了"指针指向的是哪类对象"。

    (实际上,

    typeid
    也可以用于其他类型,例如结构体,函数指针,各种类型,只要
    sizeof
    能接受的,
    typeid
    都能接受)

    用法:

    typeid(Magnificent)==typeid(*pg)

    如果

    pg
    是一个空指针,则会引发
    bad_typeid
    异常。

    type_info
    类中包含一个
    name()
    接口,该接口返回字符串,一般情况下是类的名称。

    cout<<typeid(*pg).name()<<". "

    例子:

    //RTTI2.cpp
    #include<iostream>
    #include<cstdlib>
    #include<ctime>
    #include<typeinfo>
    using namespace std;
    class Grand
    {
    protected:
        int hold;
    public:
        Grand(int h=0):hold(h){};
        virtual void Speak() const {cout<<"I am Grand class!
    ";}
    };
    class Superb:public Grand
    {
    public:
        Superb(int h=0):Grand(h){}
        virtual void Speak() const {cout<<"I am a superb class!
    ";}
        virtual void Say() const {cout<<"the value: "<<Grand::hold<<endl;}
    };
    class Magnificent:public Superb
    {
    private:
        char ch;
    public:
        Magnificent(int h=0,char c='A'):Superb(h),ch(c){}
        virtual void Speak() const{cout<<"I am a Magnificent class!
    ";}
        virtual void Say() const{cout<<"the character: "<<ch<<endl<<"the value: "<<Grand::hold<<endl;}
    };
    Grand *GetOne();
    int main()
    {
        srand(time(0));
        Grand *pg;
        Superb *ps;
        for(int i=0;i<5;i++)
        {
            pg=GetOne();
            cout<<"Now processing type "<<typeid(*pg).name()<<".
    ";
            pg->Speak();
            if(ps=dynamic_cast<Superb*>(pg))
            {
                ps->Say();
            }
            if(typeid(Magnificent)==typeid(*pg))
            {
                cout<<"Yes,you are really magnificent.
    ";
            }
        }
        delete pg;
        return 0;
    }
    Grand * GetOne()
    {
        Grand *p;
        switch (rand()%3)
        {
            case 0:
                p=new Grand(rand()%100);
                break;
            case 1:
                p=new Superb(rand()%100);
                break;
            case 2:
                p=new Magnificent(rand()%100,'A'+rand()%26);
                break;
        }
        return p;
    }

    Now processing type 6Superb.      
    I am a superb class!
    the value: 64
    Now processing type 11Magnificent.
    I am a Magnificent class!
    the character: Y
    the value: 30
    Yes,you are really magnificent.   
    Now processing type 5Grand.       
    I am Grand class!
    Now processing type 11Magnificent.
    I am a Magnificent class!
    the character: C
    the value: 3
    Yes,you are really magnificent.   
    Now processing type 5Grand.       
    I am Grand class!

    注意,你可能发现了

    typeid
    远比
    dynamic_cast
    优秀的多,
    typeid
    会直接告诉你类型是什么,而
    dynamic_cast
    只告诉你是否可以类型转换。但是
    typeid
    的效率比
    dynamic_cast
    低很多,所以,请优先考虑
    dynamic_cast
    和虚函数,如果不行才使用
    typeid

    2. cast运算符

    C语言中的类型转换符太过随意,C++创始人认为,我们应该使用严格的类型转换,则C++提供了4个类型转换运算符:

    • dynamic_cast

    • const_cast

    • static_cast

    • reinterpret_cast

    dynamic_cast
    运算符已经介绍过了,它的用途是,使得类层次结构中进行向上转换,而不允许其他转换。

    const_cast
    运算符,用于除去或增加 类型的
    const
    volatile
    属性。

    它的语法是:

    const_cast<type-name>(expression)

    如果类型的其他方面也被修改,则上述类型转换就会出错,也就是说,除了

    cv限定符
    可以不同外,
    type_name
    expression
    的类型必须相同。

    提供该运算符的目的是:有时候我们需要一个值:它在大多数情况下是常量,而有时候我们需要更改它。

    看一个有趣的例子:

    //cast运算符1.cpp
    //cast运算符1.cpp
    #include <iostream>
    int main()
    {
        using std::cout;
        using std::endl;
        volatile const int a=100;
        volatile const int & ra=a;//无法通过ra修改a
        int &change_a=const_cast<int &>(ra);//可以通过change_a修改a;
        change_a=255;
        cout<<a<<endl;
    }

    上面最后这个

    a
    常量被修改成255,这是我们预期的结果。但是如果我把
    volatile
    关键词去掉,那么
    a
    的值还是100。其实是编译器在这里玩了个小聪明,
    const int a=100
    ,编译器认为a就不会发生改变了,所以就把a放在了寄存器中,因为访问寄存器要比访问内存单元快的多,每次都从寄存器中取数据,但是后来a在内存中发生变化后,寄存器中的数据没有发生变化,所以打印的是寄存器中的数据。解决这一问题,就使用
    volatile
    关键词,那么对a的存取都是直接对内存做操作的。

    static_cast
    运算符

    它的语法是:

    static_cast<type-name>(expression)

    仅当

    type-name
    可被隐式转换成
    expression
    所属的类型或者
    expression
    可以隐式转换成
    type-name
    类型时,上述转换才合法,否则出现
    bad_cast
    异常。

    例如,枚举值可以隐式转换成整型,那么整型就可以通过

    static_cast
    转换成枚举型。

    总之,

    static_cast
    只允许"相似"的类型间的转换,而不允许"差异很大"的类间的转换。

    reinterpret_cast
    运算符:重新诠释类型,进行疯狂的类型转换

    它的语法时:

    reinterpret_cast<type-name>(expression)

    它可以进行类型间"不可思议"的互换,但是它不允许删除

    const
    ,也不允许进行丧失数据的转换

    例如下面这两个例子:

    #include<iostream>
    int main()
    {
        using namespace std;
        struct dat {short a; short b;};
        long value =0xA224B118;
        dat *pd=reinterpret_cast<dat*>(&value);
        cout<<hex<<pd->a;
    }
    #include<iostream>
    void fun()
    {
        std::cout<<"hello world!
    ";
    }
    int main()
    {
        using namespace std;
        void (*foo)();
        foo=fun;
        int* p=reinterpret_cast<int*>(foo);
    }

    通常,

    reinterpret_cast
    转换符适用于底层编程技术,是不可移植的,因为不同系统可能使用不同大小,不同顺序来存储同一类型的数据。

    总之,C语言类型转换比

    reinterpret_cast
    还要"疯狂",非常不安全,所以推荐使用
    cast
    运算符来实现类型转换。

    相关文章