RTTI (Runtime Type Information) 是 C++ 提供的一个特性,允许程序在运行时获取对象的类型信息。这个特性主要通过两个运算符实现:typeid
和 dynamic_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 的使用场景
- 多态性处理:在处理继承层次结构时,需要确定对象的具体类型。
- 类型安全的转换:需要安全地将基类指针转换为派生类指针。
- 调试和日志:在调试过程中获取对象的类型信息。
RTTI 的性能开销
使用 RTTI 会带来一定的性能开销,因为:
- 需要存储额外的类型信息
- 运行时类型检查需要额外的计算
- 可能增加二进制文件的大小
注意事项
- 要使用 RTTI,类必须至少有一个虚函数(通常将析构函数声明为虚函数)
- 某些编译器可能提供禁用 RTTI 的选项(如 g++ 的
-fno-rtti
) - 过度使用 RTTI 可能表明设计存在问题,应该考虑使用多态性来替代