dynamic_cast 在无虚函数的类上返回 nullptr,因 RTTI 仅对多态类型(含虚函数)生效;基类需有虚析构或虚函数,且目标类型须完整定义、继承关系合法、RTTI 开启;引用转换则抛 bad_cast 异常。

dynamic_cast 为什么在某些指针上返回 nullptr
不是所有指针都能用 dynamic_cast 安全转换,它只对「多态类型」有效——也就是至少有一个虚函数的类。如果源类型没虚函数表,dynamic_cast 在指针场景下直接返回 nullptr,不报错也不抛异常。
常见错误现象:dynamic_cast 结果为 nullptr,但代码没检查就直接解引用,导致段错误。
- 确保基类有虚析构函数或任意虚函数(哪怕只是
virtual ~Base() = default;) - 转换目标必须是完整定义的类类型,不能是前向声明(
class Derived;不够) - 转换方向只能是向上(子类→父类)、向下(父类→子类)或横向(同层兄弟类之间),且必须存在继承关系
- 编译时需开启 RTTI(如 GCC/Clang 默认开启;MSVC 用
/GR,禁用则整个dynamic_cast失效)
dynamic_cast 引用类型抛 bad_cast 而不是返回 nullptr
引用转换失败时,dynamic_cast 不会返回空值,而是直接抛出 std::bad_cast 异常。这和指针行为完全不同,也是最容易踩坑的地方。
使用场景:你明确期望转换一定成功,想让失败立刻中断流程并捕获异常;但更多时候,用指针 + nullptr 检查更可控。
立即学习“C++免费学习笔记(深入)”;
- 引用转换前必须确保对象真实存活且类型匹配,否则程序终止(未捕获异常)
- 若不确定类型,优先用指针形式:
auto* d = dynamic_cast<derived>(base_ptr); if (d) { ... }</derived> - 引用转换适合断言场景,例如单元测试中验证对象确切类型:
const auto& d = dynamic_cast<const derived>(obj);</const>
为什么 virtual 函数缺失会导致 dynamic_cast 编译失败或运行时失效
dynamic_cast 依赖运行时类型信息(RTTI),而 RTTI 只为带虚函数的类生成。没有虚函数,编译器可能不生成 type_info,导致转换逻辑“看不见”实际类型。
参数差异:即使两个类结构完全一样,一个有虚函数、一个没有,前者可被 dynamic_cast 安全识别,后者不行。
- 最简方案:给基类加个虚析构函数(
virtual ~Base() = default;),既满足多态要求,又防内存泄漏 - 不要试图靠空虚函数“打补丁”,比如
virtual void _rtti_helper() {}—— 没必要,虚析构就够了 - 注意:纯虚类(如接口)也必须有虚函数(哪怕是纯虚析构:
virtual ~Interface() = 0;),否则仍不可 cast
替代方案:static_cast 和 reinterpret_cast 的适用边界
当 dynamic_cast 不能用或太重(比如嵌入式环境关了 RTTI),就得选别的。但它们不检查类型安全,出错就是未定义行为。
性能 / 兼容性影响:static_cast 是零成本转换,编译期完成;reinterpret_cast 是强制重解释比特位,极度危险。
- 只在确定继承关系且方向明确时用
static_cast(例如已知base_ptr实际指向Derived) -
reinterpret_cast绝对不要用于类层次转换——哪怕看起来“地址一样”,vtable 偏移、多重继承布局都可能导致崩溃 - 现代 C++ 中,更推荐用
std::visit+std::variant替代深度类型判断,避免裸 cast
真正麻烦的不是语法怎么写,而是得时刻清楚当前对象的动态类型是否真能被识别——RTTI 不是魔法,它只忠于虚函数表的存在与否和编译器是否保留了类型元数据。









