C++ 标准库typeinfo
下面对 C++ 标准库中 <typeinfo>
头文件提供的运行时类型识别(RTTI)支持做一次系统、深入的梳理,包括typeid
操作符、std::type_info
类、相关异常类型、典型用法与实践建议。
一、概述
<typeinfo>
依赖于编译器的运行时类型识别(RTTI)机制,允许在程序运行期间查询对象或表达式的类型信息。- 主要提供:
typeid
操作符std::type_info
类- 相关异常:
std::bad_typeid
、std::bad_cast
注意:在多数编译器中,RTTI 默认开启;可通过编译选项(如 GCC 的
-fno-rtti
)关闭,关闭后<typeinfo>
功能受限。
二、typeid
操作符
1. 基本语法
#include <typeinfo>
typeid(expr) // expr 为任意可求类型的表达式
typeid(T) // T 为类型名
- 返回:一个对
const std::type_info
的引用,表示该表达式或类型的实际类型信息。 - 用例:
int i = 0; double d = 3.14; const std::type_info &ti1 = typeid(i); const std::type_info &ti2 = typeid(double);
2. 多态类型上的动态识别
- 当
expr
是一个多态类型(即至少含有一个虚函数)的左值引用或指针时,typeid(expr)
会返回动态类型(dynamic type);否则返回静态类型(static type)。 - 示例:
struct Base { virtual ~Base() = default; }; struct D1 : Base {}; Base* bp = new D1; std::cout << typeid(*bp).name(); // 输出 D1 的类型名(动态识别)
如果
bp
是空指针,则typeid(*bp)
会抛出std::bad_typeid
异常。
三、std::type_info
类
标准定义了一个不可派生、可复制的类 std::type_info
,其常用成员如下:
成员 | 说明 |
---|---|
const char* name() const noexcept | 返回一个实现定义的类型名字符串(通常是mangled,如 "i" 表示 int ) |
bool before(const type_info& rhs) const noexcept | 按某种全序比较类型先后(但并不反映继承关系),可用于在关联容器中排序 |
bool operator==(const type_info&) const noexcept | 判断两者是否表示同一类型 |
bool operator!=(const type_info&) const noexcept | 判断是否不同 |
std::size_t hash_code() const noexcept | 返回一个与类型对应的哈希值 |
- 注意:
name()
的结果不保证可读,需要在不同平台用不同 API(如 GCC 下的abi::__cxa_demangle
)进行解码。before()
与全序排序有关,但其结果是实现定义的,不应用于继承判断。
四、相关异常
1. std::bad_typeid
- 抛出场景:对空指针解引用取
typeid
,例如typeid(*p)
当p==nullptr
时。 - 继承:派生自
std::exception
,可通过what()
获取异常消息。
2. std::bad_cast
- 抛出场景:动态类型转换失败时(
dynamic_cast<Derived&>(base_ref)
将抛此异常)。 - 与
<typeinfo>
配合,用于引用的运行时检查。
五、典型用法示例
#include <iostream>
#include <typeinfo>
#ifdef __GNUG__
# include <cxxabi.h> // GCC 下用于 demangle
#endif
// 辅助:GCC/Clang 下 demangle
std::string demangle(const char* mangled) {
#ifdef __GNUG__
int status = 0;
char* realname = abi::__cxa_demangle(mangled, nullptr, nullptr, &status);
std::string name = (status == 0 && realname) ? realname : mangled;
std::free(realname);
return name;
#else
return mangled; // 其他编译器直接返回
#endif
}
struct Base { virtual ~Base() = default; };
struct Derived : Base {};
void printType(const Base* p) {
try {
const std::type_info& ti = typeid(*p);
std::cout << "Dynamic type: " << demangle(ti.name()) << "\n";
} catch (const std::bad_typeid& e) {
std::cout << "bad_typeid: " << e.what() << "\n";
}
}
int main() {
int x = 42;
std::cout << "int is: " << demangle(typeid(x).name()) << "\n";
Base* bp = new Derived;
printType(bp);
delete bp;
bp = nullptr;
printType(bp); // 演示 std::bad_typeid
return 0;
}
六、实践建议
- 谨慎依赖
name()
可读性- 不同编译器/版本间
name()
格式不统一,若要打印友好类型,请结合平台的 demangle API 或使用第三方库(如 Boost.TypeIndex)。
- 不同编译器/版本间
- 仅在必要时使用 RTTI
- 运行时类型识别会带来额外开销;若可通过设计模式(如虚函数接口、访问者模式)替代,优先考虑静态多态或手动标记。
- 异常安全
- 对多态对象指针解引用前检查是否为
nullptr
,或捕获std::bad_typeid
。
- 对多态对象指针解引用前检查是否为
- 避免
before()
做继承判断- 若要判断派生关系,请使用
dynamic_cast
(对指针返回nullptr
,对引用抛std::bad_cast
)。
- 若要判断派生关系,请使用
- 配合模板元编程
- 对模板中类型进行编译期判断(如
<type_traits>
)通常更高效;仅对运行期多态对象才用<typeinfo>
。
- 对模板中类型进行编译期判断(如
通过以上对 <typeinfo>
中 RTTI 支持的全面梳理,希望帮助你在需要运行时类型检查、动态类型转换和类型信息打印等场景中正确、高效地使用标准库功能。祝编码顺利!