C++超详细讲解RTTI和cast运算符的使用

 更新时间:2022年08月19日 17:06:15   作者:Shawn-Summer  
RTTI(Runtime Type Identification)是“运行时类型识别”的意思。C++引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型,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!\n";}
};
class Superb:public Grand
{
public:
    Superb(int h=0):Grand(h){}
    virtual void Speak() const {cout<<"I am a superb class!\n";}
    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!\n";}
    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()<<".\n"

例子:

//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!\n";}
};
class Superb:public Grand
{
public:
    Superb(int h=0):Grand(h){}
    virtual void Speak() const {cout<<"I am a superb class!\n";}
    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!\n";}
    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()<<".\n";
        pg->Speak();
        if(ps=dynamic_cast<Superb*>(pg))
        {
            ps->Say();
        }
        if(typeid(Magnificent)==typeid(*pg))
        {
            cout<<"Yes,you are really magnificent.\n";
        }
    }
    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运算符,用于除去或增加 类型的constvolatile属性。

它的语法是:

const_cast<type-name>(expression)

如果类型的其他方面也被修改,则上述类型转换就会出错,也就是说,除了cv限定符可以不同外,type_nameexpression的类型必须相同。

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

看一个有趣的例子:

//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!\n";
}
int main()
{
    using namespace std;
    void (*foo)();
    foo=fun;
    int* p=reinterpret_cast<int*>(foo);
}

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

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

到此这篇关于C++超详细讲解RTTI和cast运算符的使用的文章就介绍到这了,更多相关C++ RTTI和cast内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++的多态和虚函数你真的了解吗

    C++的多态和虚函数你真的了解吗

    这篇文章主要为大家详细介绍了C++的多态和虚函数,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02
  • Qt5 串口类QSerialPort的实现

    Qt5 串口类QSerialPort的实现

    在Qt5以上提供了QtSerialPort模块,方便编程人员快速的开发应用串口的应用程序。本文主要介绍了Qt5 串口类QSerialPort的实现,,感兴趣的可以了解一下
    2022-05-05
  • 详解 linux c++的编译器g++的基本使用

    详解 linux c++的编译器g++的基本使用

    这篇文章主要介绍了详解 linux c++的编译器g++的基本使用的相关资料,需要的朋友可以参考下
    2017-01-01
  • c++ lambda捕获this 导致多线程下类释放后还在使用的错误问题

    c++ lambda捕获this 导致多线程下类释放后还在使用的错误问题

    Lambda表达式是现代C++的一个语法糖,挺好用的。但是如果使用不当,会导致内存泄露或潜在的崩溃问题,这里总结下c++ lambda捕获this 导致多线程下类释放后还在使用的错误问题,感兴趣的朋友一起看看吧
    2023-02-02
  • C/C++内存泄漏原因分析与应对方法

    C/C++内存泄漏原因分析与应对方法

    内存泄漏会导致当前应用程序消耗更多的内存,使得其他应用程序可用的内存更少了,那么为什么会内存泄漏,我们应该怎样应对内存泄漏,所以接下来就给大家详细介绍一下C++内存泄漏原因分析与应对方法,需要的朋友可以参考下
    2023-07-07
  • opencv配置的完整步骤(win10+VS2015+OpenCV3.1.0)

    opencv配置的完整步骤(win10+VS2015+OpenCV3.1.0)

    OpenCV是计算机视觉中经典的专用库,其支持多语言、跨平台,功能强大,这篇文章主要给大家介绍了关于opencv配置(win10+VS2015+OpenCV3.1.0)的相关资料,需要的朋友可以参考下
    2021-06-06
  • C语言零基础入门(2)

    C语言零基础入门(2)

    这篇文章主要为大家详细介绍了C语言零基础入门的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • C语言中scanf函数与空格回车的用法说明

    C语言中scanf函数与空格回车的用法说明

    这篇文章主要介绍了C语言中scanf函数与空格回车的用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • Pipes实现LeetCode(195.第十行)

    Pipes实现LeetCode(195.第十行)

    这篇文章主要介绍了Pipes实现LeetCode(195.第十行),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • 浅谈C++中的string 类型占几个字节

    浅谈C++中的string 类型占几个字节

    本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节
    2013-08-08

最新评论