C++ RTTI (运行时类型信息)

RTTI (Runtime Type Information) 是 C++ 提供的一个特性,允许程序在运行时获取对象的类型信息。这个特性主要通过两个运算符实现:typeiddynamic_cast

typeid 运算符

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

cpp 复制代码
#include <typeinfo>
#include <iostream>

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

class Derived : public Base {};

int main() {
    Base* ptr = new Derived();
    
    // 获取类型信息
    std::cout << typeid(*ptr).name() << std::endl;  // 输出: Derived
    
    delete ptr;
    return 0;
}

dynamic_cast 运算符

dynamic_cast 用于在继承层次结构中进行安全的向下转型。它会在运行时检查转换是否有效,如果转换失败则返回 nullptr(对于指针)或抛出 std::bad_cast 异常(对于引用)。

cpp 复制代码
class Base {
public:
    virtual ~Base() {}
};

class Derived1 : public Base {};
class Derived2 : public Base {};

int main() {
    Base* ptr = new Derived1();
    
    // 安全的向下转型
    Derived1* d1 = dynamic_cast<Derived1*>(ptr);    // 成功
    Derived2* d2 = dynamic_cast<Derived2*>(ptr);    // 返回 nullptr
    
    delete ptr;
    return 0;
}

替代方案1 - 子类型识别

cpp 复制代码
class Base {
public:
    virtual ~Base() {}
    virtual Derived1* getDerived1() {
        return nullptr;
    }

    virtual Derived2* getDerived1() {
        return nullptr;
    }
};

class Derived1 : public Base {
    Derived1* getDerived1() override {
        return this;
    } 
};
class Derived2 : public Base {
    Derived2* getDerived2() override {
        return this;
    } 
};

替换方案2 - 类标识

cpp 复制代码
#include <array>
#include <cstdint>
#include <iostream>

// 基类
class Shape {
public:
    virtual ~Shape() = default;

    // RTTI 相关
    static constexpr std::uint32_t kBaseTypeId = 0;
    static inline std::uint32_t nextTypeId = 1;
    
    // 基类的类型 ID 数组
    inline static auto kTypeIds = std::array<std::uint32_t, 1>{kBaseTypeId};

    // 继承父类的类型 ID
    template <std::size_t Size>
    static std::array<std::uint32_t, Size + 1> extendTypeIds(const std::array<std::uint32_t, Size>& typeIds) {
        std::array<std::uint32_t, Size + 1> newTypeIds;
        newTypeIds[0] = nextTypeId++;
        std::copy_n(typeIds.data(), Size, newTypeIds.data() + 1);
        return newTypeIds;
    }

    // 类型匹配检查
    [[nodiscard]] bool match(std::uint32_t typeId) const {
        auto* ptr = getTypeIds();
        while (true) {
            if (*ptr == kBaseTypeId) {
                return false;
            } else if (*ptr == typeId) {
                return true;
            }
            ++ptr;
        }
    }

    // 虚函数接口
    [[nodiscard]] virtual uint32_t* getTypeIds() const = 0;

    // 形状的通用方法
    virtual void draw() const = 0;
};

// 宏定义,简化类型 ID 的实现
#define SHAPE_TYPE_IDS_EXTEND(Super) \
    inline static auto kTypeIds = Shape::extendTypeIds(Super::kTypeIds); \
    [[nodiscard]] uint32_t* getTypeIds() const override { \
        return kTypeIds.data(); \
    }

// 圆形类
class Circle : public Shape {
public:
    SHAPE_TYPE_IDS_EXTEND(Shape)

    void draw() const override {
        std::cout << "Drawing Circle" << std::endl;
    }
};

// 矩形类
class Rectangle : public Shape {
public:
    SHAPE_TYPE_IDS_EXTEND(Shape)

    void draw() const override {
        std::cout << "Drawing Rectangle" << std::endl;
    }
};

// 彩色圆形类(继承自圆形)
class ColoredCircle : public Circle {
public:
    SHAPE_TYPE_IDS_EXTEND(Circle)

    void draw() const override {
        std::cout << "Drawing Colored Circle" << std::endl;
    }
};

// 使用示例
int main() {
    // 创建一些形状
    Circle circle;
    Rectangle rect;
    ColoredCircle coloredCircle;

    // 类型检查
    std::cout << "Is circle a Circle? " << circle.match(circle.getTypeIds()[0]) << std::endl;
    std::cout << "Is circle a Rectangle? " << circle.match(rect.getTypeIds()[0]) << std::endl;
    std::cout << "Is coloredCircle a Circle? " << coloredCircle.match(circle.getTypeIds()[0]) << std::endl;
    std::cout << "Is coloredCircle a ColoredCircle? " << coloredCircle.match(coloredCircle.getTypeIds()[0]) << std::endl;

    return 0;
}

RTTI 的使用场景

  1. 多态性处理:在处理继承层次结构时,需要确定对象的具体类型。
  2. 类型安全的转换:需要安全地将基类指针转换为派生类指针。
  3. 调试和日志:在调试过程中获取对象的类型信息。

RTTI 的性能开销

使用 RTTI 会带来一定的性能开销,因为:

  1. 需要存储额外的类型信息
  2. 运行时类型检查需要额外的计算
  3. 可能增加二进制文件的大小

注意事项

  1. 要使用 RTTI,类必须至少有一个虚函数(通常将析构函数声明为虚函数)
  2. 某些编译器可能提供禁用 RTTI 的选项(如 g++ 的 -fno-rtti
  3. 过度使用 RTTI 可能表明设计存在问题,应该考虑使用多态性来替代
相关推荐
木子.李3471 小时前
排序算法总结(C++)
c++·算法·排序算法
freyazzr2 小时前
C++八股 | Day2 | atom/函数指针/指针函数/struct、Class/静态局部变量、局部变量、全局变量/强制类型转换
c++
fpcc3 小时前
跟我学c++中级篇——理解类型推导和C++不同版本的支持
开发语言·c++
终焉代码4 小时前
STL解析——list的使用
开发语言·c++
DevangLic4 小时前
【 *p取出内容 &a得到地址】
c++
鑫鑫向栄4 小时前
[蓝桥杯]修改数组
数据结构·c++·算法·蓝桥杯·动态规划
鑫鑫向栄4 小时前
[蓝桥杯]带分数
数据结构·c++·算法·职场和发展·蓝桥杯
m0_552200825 小时前
《UE5_C++多人TPS完整教程》学习笔记37 ——《P38 变量复制(Variable Replication)》
c++·游戏·ue5
小wanga5 小时前
【递归、搜索与回溯】专题三 穷举vs暴搜vs回溯vs剪枝
c++·算法·机器学习·剪枝
Code_流苏6 小时前
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
开发语言·c++·stl容器·课设·期末大作业·日历程序·面向对象设计