C++ 里面有四种经典的类型转换,分别是 reinterpret_cast, static_cast, dynamic_cast, const_cast.
下面简要讲一下这几种类型转换都干了什么。
变量的数据是二进制
在此之前先要知道「类型转换」代表什么。最重要的便是变量数据的存储方式——内存中的二进制。
在底层,变量并没有类型之分,大家都是 01010101 之类的二进制数据,区别来自于表示方式不同,而产生了不同类型之分。但是底层上大家都是二进制数据。
而「类型」实际上是编程语言层面的东西,在底层不存在类型这种说法,因此类型转换实际上是语言层面上进行的活动。
但注意,「可写」并不只是语言层面的东西,在底层层面同样也有可写性存在。语言中的可写指的是「保证不会被修改」之类的保证,而在底层的可写性是「绝对不能被修改」的硬约束。
reinterpret_cast
reinterpret: 重解释的。
reinterpret_cast 转换是最简单的:不改变任何二进制数据,从语言层面原样改变类型。也就是说,在底层上不进行任何修改,而是语言语法上看成了另一种类型。
这种转变方法相当简单但又十分危险——因为大部分类型之间表示方式都是不同的,除非是将其数据本来应该有的类型转换成改类型自己——也就是数据本来是用这个类型表示的,但是现在类型不对(比如说 void *),通过 reinterpret_cast 可以安全转换成正确的类型。
此外 reinterpret_cast 还可能和编译器的「严格别名约定」冲突。编译器会假设不同类型的指针不会指向同一块内存。如果你用 reinterpret_cast 把 float* 强转为 int* 然后修改,编译器在做优化时可能会乱序执行,导致你读出的数据是旧的缓存值。
因此这种类型转换通常会出现在 void * / PVOID 转换为一个确认类型的情况。
static_cast
static: 静态的。
static_cast 是在编译时进行类型转换,和 reinterpret_cast 不同的是,这里不仅在语言层面上进行转换,还同时转换底层的数据表示。
举例说如果从 float 转换成 int,static_cast 在更改类型的同时还会进行截断。另一个常见的例子是允许类层次结构中进行向上转换(派生类转基类)和向下转换(由于是静态类型转换,因此不存在运行时类型检查,基类转派生类,如果转换错误会导致未定义行为,比较危险)
dynamic_cast
dynamic: 动态的。
dynamic_cast 是基于 RTTI (运行时类型检查)来进行安全的类型转换。通过在转换前查询虚函数表确定类型,保证类型之间可以转换来达到安全转换。而如果转换不安全可能就会返回 nullptr(指针) 或抛出 std::bad_cast 异常(引用)。
dynamic_cast 比起 static_cast 和 reinterpret_cast 都更加安全——但也更加慢,因为 RTTI 要查询虚函数表来确定类型。
并且由于依赖虚函数表,因此并不能用于内置类型的转换。
const_cast
const: 常量
const_cast 用于将 const 常量变为非 const 变量。但注意,这里是语言层面的转换——如果底层数据本身就是 const(不可变的),那么强行转换后就会很危险,一旦尝试修改 const 的底层数据就会出现未定义行为。
而 const_cast 通常的正确用法是,将本来底层是非 const 的、但是语言类型却是 const 的变量转换为 非 const 的变量。