C++中static_cast和dynamic_cast的区别?(静态转型与动态安全检查)

static_cast适用于编译期可确认的类型转换,如数值转换、向上转型或自定义转换函数;dynamic_cast依赖RTTI和虚函数表,仅用于多态类型的运行时安全向下转型,失败时指针返回nullptr、引用抛bad_cast。

static_cast 适合什么场景

它做的是编译期能确认的类型转换,不涉及运行时类型检查。常见于数值类型转换、有明确继承关系的指针/引用向上转型(派生类 → 基类),或调用自定义的 operator T() 转换函数。

容易踩的坑:static_cast 允许向下转型(基类 → 派生类),但*

*不验证实际对象类型**,如果对象不是目标派生类实例,行为是未定义的。

  • int 转成 double:安全,编译器直接生成转换指令
  • Derived* 转成 Base*:安全,向上转型天然合法
  • Base* 转成 Derived*:危险,编译通过但可能崩溃

dynamic_cast 为什么需要虚函数表

dynamic_cast 依赖 RTTI(Run-Time Type Information),而 RTTI 只在至少有一个虚函数的类中才被编译器生成。它会在运行时检查对象的实际类型是否支持转换,只对多态类型(带虚函数的类)有效。

常见错误现象:dynamic_cast 对非多态类型(比如没有虚函数的 struct)编译失败,报错类似 cannot dynamic_cast ... (source type is not polymorphic)

  • 向下转型成功时返回目标类型的合法指针或引用
  • 向下转型失败时,对指针返回 nullptr,对引用抛出 std::bad_cast
  • 跨继承体系(如从一个基类转到另一个无关基类)也会失败

指针和引用的 behavior 差异

二者在失败处理上完全不同,这是最常被忽略的设计细节。

Base* b = new Base();
Derived* d1 = static_cast(b); // 编译通过,d1 指向非法内存
Derived* d2 = dynamic_cast(b); // 运行时检查,d2 == nullptr

Base& b_ref = *new Base(); try { Derived& d3 = dynamic_cast(b_ref); // 抛 std::bad_cast } catch (const std::bad_cast&) { // 必须捕获,否则程序终止 }

  • 用指针做 dynamic_cast:失败返回 nullptr,可安全判空
  • 用引用做 dynamic_cast:失败直接抛异常,不提供“检查失败”的机会
  • static_cast 对两者都不做运行时检查,失败后果由程序员承担

性能与设计意图的取舍

static_cast 零开销,dynamic_cast 有明显运行时成本:查虚函数表、遍历继承链、比较 type_info。它不是“更高级的 static_cast”,而是解决不同问题的工具。

  • 已知对象确切类型?用 static_cast,比如工厂返回 Base* 但你 100% 知道它是 Derived
  • 不确定运行时类型,且必须安全向下转型?只能用 dynamic_cast
  • 想避免 dynamic_cast 的开销又需要类型分发?考虑 Visitor 模式或 std::variant + std::visit

真正容易被忽略的是:哪怕开了 RTTI,dynamic_cast 在多重继承或虚继承下仍可能因偏移计算变慢;而关闭 RTTI(如 GCC 的 -fno-rtti)会让所有 dynamic_cast 编译失败——这点在嵌入式或游戏引擎中常被遗忘。