C++ RTTI 运行时类型信息

RTTI 运行时类型信息

RTTI(run time type identificaiton),允许程序员在运行时进行对象信息识
别。typeid /dynamic_cast 是 C++ 运行时类型信息重要组成部分。

运行时信息,来自于多态,所以以下运算符只用于基于多态的继承体系中。

RTTI(Run-Time Type Information/Run-Time Type Identification,运行时类型信息/运行时类型识别)
是一种编程语言特性,允许程序在运行时获取对象的类型信息。
这种特性在C++中尤为常见,尽管其他语言如Java和C#也提供了类似的功能。

在C++中,RTTI主要通过两个运算符来实现:

typeid运算符:用于获取一个表达式的类型信息。返回一个std::type_info对象,该对象包含类型的相关信息。

dynamic_cast运算符:用于在类层次结构中进行安全的向下转换(即从基类指针或引用转换为派生类指针或引用)。

引例

下面是一个简单的例子,展示了如何使用typeid和dynamic_cast。

#include <iostream>
#include <typeinfo>

class Base {
public:
    virtual ~Base() {} // 虚析构函数,确保RTTI正常工作
};

class Derived : public Base {
public:
    void derivedFunction() {
        std::cout << "Derived function called" << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived();

    // 使用typeid获取类型信息
    if (typeid(*basePtr) == typeid(Derived)) {
        std::cout << "basePtr points to a Derived object" << std::endl;
    }

    // 使用dynamic_cast进行安全的向下转换
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
    if (derivedPtr) {
        derivedPtr->derivedFunction();
    }

    delete basePtr;
    return 0;
}

在这个示例中,typeid用于检查basePtr指向的对象是否是Derived类型,
而dynamic_cast用于将basePtr安全地转换为Derived*类型,
以便调用Derived类中的成员函数。

需要注意的是,
RTTI在某些情况下可能会带来性能开销,
因此在性能敏感的应用中,开发者可能会选择禁用RTTI。

typeid

运算符 typeid 返回包含操作数数据类型信息的 type_info 对象的一个引用,信
息中包括数据类型的名称,要使用 typeid,程序中需要包含头文件<typeinfo>。

其中 type_info 重载了操作符 ==,!= 分别用来比较是否相等、不等,函数 name()
返回类型名称。

type_info 的拷贝和赋值均是私有的,故不可拷贝和赋值。

常用于返回检查,调试之用。

基础类型应用

//TODO: RTTI 运行时类型信息

#include <iostream>
#include <typeinfo>
using namespace std;
int main()
{
//基本类型
    cout<<typeid(char).name()<<endl;
    cout<<typeid(short).name()<<endl;
    cout<<typeid(int).name()<<endl;
    cout<<typeid(long long).name()<<endl;
    cout<<typeid(float).name()<<endl;
    cout<<typeid(double).name()<<endl;
//函数类型
    void (*pf)(int);
    cout<<typeid(pf).name()<<endl;
    //指针类型
    cout<<typeid(int*).name()<<endl;
    cout<<typeid(const int*).name()<<endl;
    cout<<typeid(int* const).name()<<endl;
    return 0;
}

输出:

c
s
i
x
f
d
PFviE
Pi
PKi
Pi

多态体系应用

引用和解引用的指针,在多态体系中,均可以通过 typeid 获得真实有效的信息。

注意在多态体系中,要想应用 typeid,必须虚析构。

引例:typeid 指针和引用



#include <iostream>
#include <typeinfo>
using namespace std;
class A
{
public:
    virtual ~A(){} // 若无此虚函数, 则称运行时信息不成立
};
class B:public A{
};
class C:public A{
};
void func(A *pa)
{
    cout<<typeid(pa).name()<<endl;
    cout<<typeid(*pa).name()<<endl;
}
void func(A &ra)
{
    cout<<typeid(ra).name()<<endl;
}
int main(int argc, char *argv[])
{
    B b;
    C c;
    A *p1 = &b;
    A *p2 = &c;
    A *p3 = &b;

    cout<<typeid(b).name()<<endl; //1B
    cout<<typeid(p1).name()<<endl; //P1A
    cout<<typeid(*p1).name()<<endl; //1B

    if(typeid(*p1) != typeid(*p2)){
        cout<<"p1和p2的类型不同"<<endl;
    }//p1和p2的类型不同


    A & r1 = b;
    A & r2 = c;
    A & r3 = b;


    cout<<typeid(r1).name()<<endl; //1B
    cout<<typeid(r2).name()<<endl; //1C
    cout<<typeid(r3).name()<<endl; //1B

    if(typeid(r1) != typeid(r2)){
        cout<<"p1和p2的类型不同"<<endl;
    }//p1和p2的类型不同

    return 0;
}

注意事项

确保基类定义了至少一个虚函数(虚析构也算)。

不要将 typeid 作用于指针,应该作用于引用,或解引用的指针。

typeid 是一个运算符,而不是函数。

typeid 运算符返回的 type_info 类型,其拷贝构造函数和赋值运算函数都声明为
private 了,这意味着其不能用于 stl 容器,所以我们一般不能不直接保存 type_info
信息,而保存 type_info 的 name 信息


指针类型:
当 typeid 作用于指针时,它返回的是指针类型本身。例如,如果 a 和 b 都是 Base* 类型的指针,那么 typeid(a) 和 typeid(b) 返回的都是 Base* 类型。

对象类型:
当 typeid 作用于指针所指向的对象时,它返回的是对象的动态类型。动态类型是指对象在运行时的实际类型,而不是静态类型。例如,如果 a 是一个指向 Base 类的指针,但实际指向的是一个 Derived 类的对象(Derived 是 Base 的派生类),那么 typeid(*a) 返回的是 Derived 类型。

空指针异常:
如果 typeid 作用于一个解引用的指针(即 *ptr),而这个指针的值为空(即 nullptr),那么 typeid 会抛出 bad_typeid 异常。这是因为解引用一个空指针是非法操作,typeid 在这种情况下无法确定对象的类型。

总结来说,typeid 运算符在处理指针和对象时表现出不同的行为:
对于指针,返回指针类型本身。
对于指针所指向的对象,返回对象的动态类型。
如果指针为空,解引用时会抛出 bad_typeid 异常。

dynamic_cast 向下转型

应用于类的指针、类的引用或者 void*。
dynamic_cast 运算符可以在执行期决定真正的类型。
如果 downcast 是安全的(也就说,如果基类指针或者引用确实
指向一个派生类对象)这个运算符会传回适当转型过的指针。
如果 downcast 不安全,这个运算符会传回空指针(也就是说,
基类指针或者引用没有指向一个派生类对象)。
dynamic_cast 主要用于类层次间的上行转换和下行转换,还可
以用于类之间的交叉转换。

引例:

#include <iostream>
#include <typeinfo>

using namespace std;

// 定义类A,它有一个虚析构函数
class A
{
public:
    virtual ~A(){}  // 虚析构函数,用于支持多态
};

// 定义类B,继承自A
class B : public A {};

// 定义类C,继承自A
class C : public A {};

// 定义类D,不继承自A
class D {};

int main(int argc, char *argv[])
{
    // 创建一个B类的对象b
    B b;
    
    // 将b的地址赋值给指向A类的指针pa
    A *pa = &b;
    
    // 尝试将pa向下转型为B类的指针
    B* pb = dynamic_cast<B*>(pa);
    if(pb != NULL)
        cout << "pb != NULL" << endl;  // 如果转型成功,输出pb != NULL
    else
        cout << "pb == NULL" << endl;  // 如果转型失败,输出pb == NULL
    
    // 尝试将pa向下转型为C类的指针
    C *pc = dynamic_cast<C*>(pa);
    if(pc != NULL)
        cout << "pc != NULL" << endl;  // 如果转型成功,输出pc != NULL
    else
        cout << "pc == NULL" << endl;  // 如果转型失败,输出pc == NULL
    
    // 尝试将pa向下转型为D类的指针
    D *pd = dynamic_cast<D*>(pa);
    if(pd != NULL)
        cout << "pd != NULL" << endl;  // 如果转型成功,输出pd != NULL
    else
        cout << "pd == NULL" << endl;  // 如果转型失败,输出pd == NULL
    
    return 0;
}
输出:

pb != NULL
pc == NULL
pd == NULL

交叉转换

通过交叉转换,可以在多重继承的类层次结构中灵活地在不同基类之间进行转换,从而实现更复杂的设计和功能。


#include <iostream>
#include <typeinfo>

class Base1 {
public:
    virtual ~Base1() {}
};

class Base2 {
public:
    virtual ~Base2() {}
};

class Derived : public Base1, public Base2 {
};

int main() {
    Derived* derivedPtr = new Derived();

    // 从 Derived* 转换为 Base1*(上行转换)
    Base1* base1Ptr = dynamic_cast<Base1*>(derivedPtr);
    if (base1Ptr) {
        std::cout << "转换为 Base1* 成功" << std::endl;
    } else {
        std::cout << "转换为 Base1* 失败" << std::endl;
    }

    // 从 Derived* 转换为 Base2*(上行转换)
    Base2* base2Ptr = dynamic_cast<Base2*>(derivedPtr);
    if (base2Ptr) {
        std::cout << "转换为 Base2* 成功" << std::endl;
    } else {
        std::cout << "转换为 Base2* 失败" << std::endl;
    }

    // 从 Base1* 转换为 Base2*(交叉转换)
    Base2* crossCastPtr = dynamic_cast<Base2*>(base1Ptr);
    if (crossCastPtr) {
        std::cout << "交叉转换为 Base2* 成功" << std::endl;
    } else {
        std::cout << "交叉转换为 Base2* 失败" << std::endl;
    }

    delete derivedPtr;
    return 0;
}


输出:
转换为 Base1* 成功
转换为 Base2* 成功
交叉转换为 Base2* 成功

注意事项

虚函数:交叉转换依赖于虚函数,因为 dynamic_cast 需要运行时类型信息(RTTI)来确定对象的实际类型。
多重继承:交叉转换通常发生在多重继承的情况下,即一个类同时继承自多个基类。
安全性:dynamic_cast 在运行时进行类型检查,确保转换的安全性。
如果转换不合法,
对于指针类型,dynamic_cast 会返回 nullptr;
对于引用类型,会抛出 std::bad_cast 异常。
  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

可能只会写BUG

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值