C++ 显式类型转换
C 风格的 (T)expr 一个写法背后可能是四种完全不同的操作:数值转换、类层次导航、去除 const、位模式重解释。编译器遇到它时会按固定顺序依次尝试 const_cast、static_cast、static_cast 加 const_cast、reinterpret_cast、reinterpret_cast 加 const_cast,第一个能编译通过的就生效——写的人未必知道实际发生的是哪一种,读的人更无从判断风险,也没法用 grep 找出代码里所有的转换点。
C++ 把这四种语义拆成四个操作符,本质是要求程序员显式声明转换意图。意图不同,安全性、开销、审查方式完全不同:
static_cast:类型之间的值如何映射?——编译期的良定义转换dynamic_cast:这个基类指针实际指向谁?——向运行期要答案const_cast:cv 限定符能不能去掉?——唯一的 cv 后门reinterpret_cast:这段内存换个视角看是什么?——位模式重解释
static_cast:编译期的良定义转换
static_cast 执行语言规则本身就允许的转换:数值类型互转、void* 往返、继承体系上溯、调用显式构造函数和转换运算符。全部工作在编译期完成,零运行时开销;编译器只检查转换是否合法,不保证结果是否正确。
1 | |
需要留意的是 static_cast 也能做下溯(基类指针转派生类指针),前提是程序员自己保证对象确实是目标类型——猜对了零开销,猜错了就是未定义行为。这正是它和 dynamic_cast 的分界线:下溯时类型已知用 static_cast,类型未知必须 dynamic_cast。
它做不了的事:去除 const(归 const_cast),无关联指针类型互转(归 reinterpret_cast)。
dynamic_cast:向运行期要答案
四种 cast 中唯一带运行期检查的。它借助 RTTI 回答一个问题:这个基类子对象实际上是不是目标类型?指针版本失败返回 nullptr,引用版本失败抛 std::bad_cast:
1 | |
三个前提与代价:
- 要求多态类型:类中至少有一个虚函数,否则编译失败——RTTI 挂在虚表上。
- 有运行时开销:需要在继承图中查询,远比
static_cast慢。 - 引用版本必须处理异常:
std::bad_cast无法静默忽略。
dynamic_cast 有一项独占能力:在多继承或虚继承体系中做交叉转换(cross-cast),即从一个基类子对象转到另一个无继承关系的基类子对象。这类转换编译期无法静态建立,只能靠运行期查询。
工程上的经验法则:dynamic_cast 零星出现是工具,成片出现是设计信号——本该用虚函数分派或 visitor 的地方,在用类型判断硬分支。
const_cast:唯一的 cv 后门
const_cast 只能做一件事:加、去 const 或 volatile 限定符,不能改变任何底层类型。它的合法场景基本只有两类。
对接没有标注 const 的旧接口。旧函数承诺只读但签名没写 const,去限定符只为通过编译:
1 | |
const 重载之间的委托。经典的 const / 非 const 成员函数去重手法:const 版本把 this 去限定后复用非 const 实现,再把结果加回 const,逻辑只写一份:
1 | |
危险点只有一个,但必须牢记:修改本体为 const 的对象是未定义行为。判断依据是对象的定义,而非指针的类型——const int x = 0; 哪怕骗过编译器拿到 int*,写它就是 UB;反过来,对象本体并非 const,通过 const 指针去限定后再写是合法的。
reinterpret_cast:位模式重解释
最不安全的转换:编译器不做任何检查,无条件按程序员的要求把位模式换个类型解释。它能做指针互转、指针与整数互转、函数指针互转,其余一概不管。
合法用途集中在底层编程:序列化与网络协议中的字节视图(char*、unsigned char* 别名本就是规则允许的)、用 uintptr_t 暂存指针值、placement new 的原始缓冲区:
1 | |
使用时记住三条边界:
- 转出去的指针唯一安全的用法是转回原类型再用;直接以新类型解引用,大多违反别名规则或对齐要求。
- 函数指针转换同理:可以转,但只能转回原签名后调用,中间形态不可调用。
- 结果依赖平台(指针宽度、字节序、对齐),可移植性全靠自己保证。
除非在写驱动、协议栈、序列化层,否则看到 reinterpret_cast 的第一反应应该是:有没有别的办法。
如何选择
flowchart TB
Q{"转换意图"} --> A["去除 const/volatile"]
Q --> B["无关联类型<br/>位级重解释"]
Q --> C{"类层次导航"}
A --> A1["const_cast"]:::cast
B --> B1["reinterpret_cast"]:::cast
C -->|"向上,或下溯且类型已知"| C1["static_cast"]:::cast
C -->|"下溯,需运行期确认"| C2["dynamic_cast"]:::cast
Q --> D["数值转换 / 显式构造"] --> C1
classDef cast fill:#e3f2fd,stroke:#1565c0
classDef intent fill:#fff3e0,stroke:#ef6c00
class A,B,D intent| cast | 回答的问题 | 安全性 | 运行时检查 | 典型场景 |
|---|---|---|---|---|
static_cast | 值如何映射 | 中 | 无 | 数值转换、上溯、显式构造 |
dynamic_cast | 实际指向谁 | 高 | 有 | 多态下溯、交叉转换 |
const_cast | cv 能否去掉 | 低 | 无 | 旧接口对接、const 委托 |
reinterpret_cast | 位模式怎么读 | 极低 | 无 | 字节视图、指针整数互转 |
工程实践的优先级:
- 默认
static_cast:意图明确、零开销,能编译就是良定义。 - 多态下溯用
dynamic_cast:为运行时确认付费,别拿static_cast赌类型。 const_cast只用于接口兼容与 const 委托:出现其他用途,多半是设计有问题。reinterpret_cast留给底层代码:业务代码里出现就该追问原因。
另外值得单独一提:std::any_cast 用于从 C++17 的 std::any 中取回值,它与这四个 cast 并非一家,而是类型擦除容器的配套工具,详见 C++17 any 与 variant。






