前言

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
2
3
int x = 10;
auto y = (bool)x; // C 风格类型转换
auto z = bool(x); // C++ 风格传统类型转化

static_cast 静态类型转换

这是最常用的类型转换,可以完成所有单一类型转换操作(复合类型的情况比较复杂)。但是不能做的操作有:

  • 不能去除指针、数组、引用的底层 cosnt,但可以添加底层 const。注意数组、引用的 const 都是底层 const 即它们没有顶层 const
  • 不能修改指针底层类型。但在不违反上一原则的前提下,能将任意指针类型转为 void * / const void *,或将 void * / const void * 转为任意指针类型。

这两种不能进行的转换可以使用后面介绍的方法转换,将在后面章节介绍。

1
2
3
4
5
6
7
int x = 10;
int * const p = &x;
auto y = static_cast<float>(x); // 可以进行任意单一类型转换 (单一类型对应复合类型)
auto q = static_cast<const int *>(p); // 可以修改顶层 const, 也可以添加底层 const
auto r = static_cast<const void *>(&p); // 可以将任意指针类型转换为 void *, 反向也可不演示了
auto s = static_cast<int *>(q); // 编译错误, 不能去除底层 const
auto t = static_cast<void *>(&p); // 编译错误, 转为 void * 但不能违背上一原则 (`&p` 类型为 int * const *, 注意如果 `&p` 类型为 int const ** 则可转为 void *)

另外,在面向对象方面,static_cast 还有其特殊性。主要包含两点:

  • 对于用户定义类型,如果定义了类型转换函数,则其类型转换是合法的,否则是非法的。
  • 如果类之间满足继承关系,则其类型转换是合法的。当然,基类到派生类的转换可能引入新的变量,这是危险的。(后面将介绍 dynamic_cast 应对这个问题)

const_cast 常量类型转换

可以任意修改 const,包括底层和顶层 const,但不能进行其他类型转换。const_cast 对应处理 static_cast 的第一条不可用原则。

1
2
3
4
int x = 10;
const int *p = &x;
auto q = const_cast<int *>(p); // 可以修改底层 const
auto r = const_cast<float *>(p); // 编译错误, 不能顺便完成类型转换

const_cast 通常用于临时修改变量的可修改性,从而保证该变量通常是不可修改的,在需要时临时修改。const_cast 的存在因此被认为是有利于程序安全的,而其他强制类型转换的出现都被认为代码存在或多或少的缺陷。当然,将 const_cast 返回值存储并长期使用依然是危险的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 一个安全使用 const_cast 的示例
class A {
int call_times;
int x;
public:
A(int x) : call_times(0), x(x) {};
int get_x() const {
const_cast<int &>(call_times)++; // 函数总体是 const 的, 但我们需要给予 call_times 一个特权
return x;
};
int get_call_times() const {
return call_times;
}
};

reinterpret_cast 重新解释类型

也用作指针、数组、引用类型的转换,在不修改底层 const 的前提下可以进行任意底层类型的修改。reinterpret_cast 是十分危险的,正如其名字一样,reinterpret_cast 只对底层进行重新解释,而不作转换,对于自定义类型也不调用类型转换函数。

1
2
3
4
// 这个例子展示了 reinterpret_cast 的危险性
int *p = new int(10);
cout << *reinterpret_cast<float *>(p) << endl;
// 输出: 1.4013e-44

另外,reinterpret_cast 其实可以通过两次 static_cast 实现。

1
2
int *p = new int(10);
cout << *static_cast<float *>(static_cast<void *>(p)) << endl;
1
2
3
4
5
// 想说明 reinterpret_cast 并不是等价于两次 static_cast 的
const int * const *p = new auto(new auto(10));
auto q = reinterpret_cast<float * const *>(p); // 编译错误, 底层 const 不能改变
auto q = static_cast<float * const *>(static_cast<const void *>(p)); // 但两次 static_cast 可以绕开这个机制, 当然两次 reinterpret_cast 也可以
cout << **q << endl;

dynamic_cast 动态类型转换

dynamic_castRTTIruntime type identification)的两个运算符之一,它作用于运行时,用于确保抽象类到派生类的安全转换。在使用抽象类时,我们通常将派生类转换为抽象类指针或引用,而虚函数将继续与派生类绑定。而 dynamic_cast 提供了安全的运行时类型转换,通常用于抽象类到具体类的转换。

当进行错误的动态类型转换时会发生错误。具体表现为一下三个方面:

  • 如果原始类型和目标类型没有运行时多态关系,则 dynamic_cast 发生编译时错误。(两个类如果存在公共抽象类,且存在虚函数继承关系,则它们存在运行时多态关系)
  • 如果转换后类型不是原类型或其一个基类,则 dynamic_cast 发生运行时错误,其中指针和引用的表现形式是不同的:
    • 如果转换类型为指针,则错误时返回空指针。
    • 如果转换类型为引用,则错误时抛出 bad_cast 异常。

需要注意的是,使用 static_cast(以及传统类型转换)也能完成这些类型转换,但其没有像 dynamic_cast 一样的确保安全性的机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 一些类的定义
class A {
public:
virtual void whoAmI() {};
};

class B : public A {
private:
int id = 0;
public:
B(int id) : A(), id(id) {};
void whoAmI() override { cout << "I am B" << endl; }
int getId() { return id; }
};

class C : public A {
public:
void whoAmI() override { cout << "I am C" << endl; }
};

class D {
public:
void whoAmI() { cout << "I am D" << endl; }
};

int main() {
// ...
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
// dynamic_cast 判别为是相同类型
A *a = new B(10);
B *b = dynamic_cast<B *>(a);
cout << b << endl;
if (b) {
b->whoAmI();
cout << b->getId() << endl;
}
/* 输出:
0x10617f0
I am B
10
*/
1
2
3
4
5
6
7
8
9
// dynamic_cast 判别为非相同类型并返回 NULL
A *a = new C;
B *b = dynamic_cast<B *>(a); // a 是 C 类型的, 而非 B 类型的, 因此返回 NULL
cout << b << endl;
if (b) {
b->whoAmI();
cout << b->getId() << endl;
}
// 输出: 0
1
2
3
4
5
6
7
8
9
10
11
12
13
// static_cast 无法判断转换正误是相当危险的
A *a = new C;
B *b = static_cast<B *>(a); // a 是 C 类型的, 而非 B 类型的, 因此返回 NULL
cout << b << endl;
if (b) {
b->whoAmI();
cout << b->getId() << endl;
}
/* 输出:
0x1a17f0
I am C
-1414812757
*/
1
2
3
4
5
6
7
8
9
// dynamic_cast 检测到编译错误
D *d = new D;
B *b = dynamic_cast<B *>(d); // 编译错误, B 和 D 不存在公共抽象类
// dynamic_cast 之所以设计这样的编译错误, 是因为可以预见 B 和 D 始终不可能是相同的, 这一点在编译时就可判断而无需等到运行时
cout << b << endl;
if (b) {
b->whoAmI();
cout << b->getId() << endl;
}