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 可能表明设计存在问题,应该考虑使用多态性来替代
相关推荐
津津有味道1 小时前
Qt C++串口SerialPort通讯发送指令读写NFC M1卡
linux·c++·qt·串口通信·serial·m1·nfc
让我们一起加油好吗2 小时前
【C++】list 简介与模拟实现(详解)
开发语言·c++·visualstudio·stl·list
傅里叶的耶2 小时前
C++系列(二):告别低效循环!选择、循环、跳转原理与优化实战全解析
c++·visual studio
Vitta_U2 小时前
MFC的List Control自适应主界面大小
c++·list·mfc
Dovis(誓平步青云)3 小时前
基于探索C++特殊容器类型:容器适配器+底层实现原理
开发语言·c++·queue·适配器·stack
pipip.5 小时前
UDP————套接字socket
linux·网络·c++·网络协议·udp
孞㐑¥9 小时前
Linux之Socket 编程 UDP
linux·服务器·c++·经验分享·笔记·网络协议·udp
水木兰亭12 小时前
数据结构之——树及树的存储
数据结构·c++·学习·算法
CoderCodingNo13 小时前
【GESP】C++四级考试大纲知识点梳理, (7) 排序算法基本概念
开发语言·c++·排序算法
秋风&萧瑟14 小时前
【C++】C++中的友元函数和友元类
c++