四种 cast 强制类型转换
前言
在 C
语言中,直接使用类型即可完成强制类型转换,形如 (int)x
。而在 C++
中强制类型转换被分为四类:static_cast
/ const_cast
/ reinterepret_cast
/ dynamic_cast
四类。当然 C++
也保留了传统类型转换。
传统类型转换
传统 C 语言类型转换具有最高的级别,它能完成我们后面将会介绍的四种 cast
支持的所有强制类型转换。但在 C++
中之所以提出四类 cast
,是因为将强制类型转换分门别类被认为是更安全的。首先,强制类型转换都是危险的,你需要清楚的知道进行转换可能带来的后果,四类 cast
一定程度上确保你正确进行转换,否则将会发生编译错误(dynamic_cast
可能会发生运行时错误)。
值得注意的是,在 C++
中提出的 static_cast
/ const_cast
/ reinterepret_cast
/ dynamic_cast
都是关键字,足见其地位之重。
最后,传统 C 语言类型转换在 C++
中又分为两种:C
风格类型转换、 C++
风格传统类型转换。其实两者并没有区别,只是在语法习惯上作区分。
1 | int x = 10; |
static_cast 静态类型转换
这是最常用的类型转换,可以完成所有单一类型转换操作(复合类型的情况比较复杂)。但是不能做的操作有:
- 不能去除指针、数组、引用的底层
cosnt
,但可以添加底层const
。注意数组、引用的const
都是底层const
即它们没有顶层const
。 - 不能修改指针底层类型。但在不违反上一原则的前提下,能将任意指针类型转为
void *
/const void *
,或将void *
/const void *
转为任意指针类型。
这两种不能进行的转换可以使用后面介绍的方法转换,将在后面章节介绍。
1 | int x = 10; |
另外,在面向对象方面,static_cast
还有其特殊性。主要包含两点:
- 对于用户定义类型,如果定义了类型转换函数,则其类型转换是合法的,否则是非法的。
- 如果类之间满足继承关系,则其类型转换是合法的。当然,基类到派生类的转换可能引入新的变量,这是危险的。(后面将介绍
dynamic_cast
应对这个问题)
const_cast 常量类型转换
可以任意修改 const
,包括底层和顶层 const
,但不能进行其他类型转换。const_cast
对应处理 static_cast
的第一条不可用原则。
1 | int x = 10; |
const_cast
通常用于临时修改变量的可修改性,从而保证该变量通常是不可修改的,在需要时临时修改。const_cast
的存在因此被认为是有利于程序安全的,而其他强制类型转换的出现都被认为代码存在或多或少的缺陷。当然,将 const_cast
返回值存储并长期使用依然是危险的。
1 | // 一个安全使用 const_cast 的示例 |
reinterpret_cast 重新解释类型
也用作指针、数组、引用类型的转换,在不修改底层 const
的前提下可以进行任意底层类型的修改。reinterpret_cast
是十分危险的,正如其名字一样,reinterpret_cast
只对底层进行重新解释,而不作转换,对于自定义类型也不调用类型转换函数。
1 | // 这个例子展示了 reinterpret_cast 的危险性 |
另外,reinterpret_cast
其实可以通过两次 static_cast
实现。
1 | int *p = new int(10); |
1 | // 想说明 reinterpret_cast 并不是等价于两次 static_cast 的 |
dynamic_cast 动态类型转换
dynamic_cast
是 RTTI
(runtime type identification
)的两个运算符之一,它作用于运行时,用于确保抽象类到派生类的安全转换。在使用抽象类时,我们通常将派生类转换为抽象类指针或引用,而虚函数将继续与派生类绑定。而 dynamic_cast
提供了安全的运行时类型转换,通常用于抽象类到具体类的转换。
当进行错误的动态类型转换时会发生错误。具体表现为一下三个方面:
- 如果原始类型和目标类型没有运行时多态关系,则
dynamic_cast
发生编译时错误。(两个类如果存在公共抽象类,且存在虚函数继承关系,则它们存在运行时多态关系) - 如果转换后类型不是原类型或其一个基类,则
dynamic_cast
发生运行时错误,其中指针和引用的表现形式是不同的:- 如果转换类型为指针,则错误时返回空指针。
- 如果转换类型为引用,则错误时抛出
bad_cast
异常。
需要注意的是,使用 static_cast
(以及传统类型转换)也能完成这些类型转换,但其没有像 dynamic_cast
一样的确保安全性的机制。
1 | // 一些类的定义 |
1 | // dynamic_cast 判别为是相同类型 |
1 | // dynamic_cast 判别为非相同类型并返回 NULL |
1 | // static_cast 无法判断转换正误是相当危险的 |
1 | // dynamic_cast 检测到编译错误 |