运行时类型识别

运行时类型识别

运行时类型识别(RTTI)主要作用是获得指针及引用变量的实际类型, 其主要操作有两种, 分别是 获取实际类型 与 安全类型转换;

获取实际类型

获取实际类型主要依靠: typeid 关键字 与 type_info 类, 示例代码如下:

类代码:

// demo.h
class Demo {
public:
    Demo() = default;
    virtual ~Demo() = default;

    virtual void disp()  {cout << "Demo" << endl;}

    inline double dispA() {return m_a;}
private:
    double m_a = 10.0;
};


class DemoDerived : public Demo {
public:
    DemoDerived() = default;
    ~DemoDerived() = default;

    virtual void disp() override {cout << "DemoDeriv" << endl;}

    inline double dispB() {return m_b;}
private:
    double m_b = 8.0;
};

主函数:

#include <iostream>
#include <typeinfo>

#include "demo.h"

using std::cout;
using std::endl;
using std::type_info;


int main()
{
    Demo* demo1 = new Demo();
    Demo* demo2 = new DemoDerived();

    const type_info& type1 = typeid (*demo1);
    const type_info& type2 = typeid (*demo2);

    cout << type1.name() << endl;
    cout << type2.name() << endl;

    delete demo1;
    delete demo2;

    return 0;
}

终端输出:

// console output : 
4Demo
11DemoDerived

不同编译器可能输出略微不同, 但是仍可看到两个指针所指向的实际类型分别为 Demo 与 DemoDerived, 其中数字表示类型名称的字符长度;

安全类型转换

在基类与继承类之间进行安全类型转换主要依靠 dynamic_cast<>() 操作, 示例代码如下;

#include <iostream>
#include <typeinfo>

#include "demo.h"

using std::cout;
using std::endl;
using std::type_info;


int main()
{
    Demo* demo1 = new Demo();
    Demo* demo2 = new DemoDerived();

    DemoDerived* demoCast1 = dynamic_cast<DemoDerived*>(demo1);
    DemoDerived* demoCast2 = dynamic_cast<DemoDerived*>(demo2);

    cout << "demoCast1 addr = " << demoCast1 << endl;
    cout << "demoCast2 addr = " << demoCast2 << endl;

    delete demo1;
    delete demo2;

    return 0;
}

上述代码终端输出:

// console output : 
demoCast1 addr = 0
demoCast2 addr = 0x55555556aed0

其中, demo2 的实际类型是 DemoDerived, 所以动态类型转换成功, demoCast2是有效地址;demo1 的实际类型 Demo, 所以动态类型转换失败, demoCast1 地址为 0, 是无效地址, 表示了这种转换是不正确的, 这正是动态类型转换的安全性所在;

而静态类型转换 static_cast 不会检查类型转换之间的安全性, 反而会引起一些潜在危险,且难以排查, 示例代码如下:

#include <iostream>
#include <typeinfo>

#include "demo.h"

using std::cout;
using std::endl;
using std::type_info;

int main()
{
    Demo* demo1 = new Demo();
    Demo* demo2 = new DemoDerived();

    DemoDerived* ptrDemoError = static_cast<DemoDerived *>(demo1);

    cout << "ptrDemoError addr = " << ptrDemoError << endl;

    cout << ptrDemoError->dispA() << endl;
    cout << ptrDemoError->dispB() << endl;

    delete demo1;
    delete demo2;

    return 0;
}

上述代码终端输出为:

// console output : 
ptrDemoError addr = 0x55555556aeb0
10
0

虽然上述代码中的类型转换是不正确的, 但是静态类型转换仍然能够获得demo1的地址, 而后其错误地使用了DemoDerived中的dispB(), 给出了错误的结果;这种错误排查难度远远大于程序意外终止等明显的问题;

所以, 在基类与继承类之间的转换中尽量只使用 dynamic_cast, 不要使用C风格的强制类型转换 与 static_cast 等不安全的类型转换, 除非自己明确知道这种类型的转换一定是正确的, 否则产生错误可能非常难以排查;

总结

运行时类型识别RTTI虽然会降低程序性能, 但是适当使用提升了程序的安全性, 对编码起到事半功倍的效果;


本文作者: 王同学