C++引入这几种强制类型变换的原因:

C风格的强制类型转换很简单,均用 Type b = (Type)a 形式转换,但是这样不安全,容易出bug而且也较难排查。比如不经意间将指向const对象的指针转换成非const对象的指针,或者将基类对象指针转成了派生类对象的指针,这种转换很容易出问题。

static_cast

类似C风格的强制转换,进行无条件转换,静态类型转换:

1)基类和子类之间的转换:其中子类指针转换为父类指针是安全的,但父类指针转换为子类指针是不安全的(基类和子类之间的动态类型转换建议用dynamic_cast)。

2)基本数据类型转换,enum,struct,int,char,float等。static_cast不能进行无关类型(如非基类和子类)指针之间的转换。

3)把任何类型的表达式转换成void类型。

4)static_cast不能去掉类型的const、volatile属性(用const_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
30

#include <iostream>

using namespace std;

struct Base {
virtual void Func() { cout << "Base Func \n"; }
};

struct Derive : public Base {
void Func() override { cout << "Derive Func \n"; }
};

int main() {
float f = 1.23;
cout << "f " << f << endl;
//基本类型转换
int i = static_cast<int>(f);
cout << "i " << i << endl;

//无关类型转换,编译错误
int *pi = static_cast<int *>(&f);

//子类指针转为父类指针
Derive d;
d.Func();
Base *b = static_cast<Base *>(&d);
b->Func();
return 0;
}
const_cast

去掉类型的const或volatile属性

1
2
3
4
5
6
7
8
9
10
11
12
13
int main() {

struct T {
int i;
};

const T a;
//a.i = 10; //直接修改const类型,编译错误
T &b = const_cast<T&>(a);
b.i = 10;

return 0;
}
dynamic_cast

有条件转换,动态类型转换,运行时检查类型安全(转换失败返回NULL):

1)安全的基类和子类之间的转换,不能用于内置的基本数据类型的强制转换。

2)基类中一定要有虚函数,否则编译不通过。

3)相同基类不同子类之间的交叉转换,但结果返回NULL。

4)其他三种都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。

用于将父类的指针或引用转换为子类的指针或引用,此场景下父类必须要有虚函数,因为dynamic_cast是运行时检查,检查需要运行时信息RTTI,而RTTI存储在虚函数表中。

需要检测有虚函数的原因:类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义。 这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(关于虚函数表的概念,详细可见<Inside c++ object model>)中,
只有定义了虚函数的类才有虚函数表。

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 我是父类
class Tfather
{
public:
virtual void f() { cout << "father's f()" << endl; }
};

// 我是子类
class Tson : public Tfather
{
public:
void f() { cout << "son's f()" << endl; }

int data; // 我是子类独有成员
};

int main()
{
Tfather father;
Tson son;
son.data = 123;

Tfather *pf;
Tson *ps;

/* 上行转换:没有问题,多态有效 */
ps = &son;
pf = dynamic_cast<Tfather *>(ps);
pf->f();

/* 下行转换(pf实际指向子类对象):没有问题 */
pf = &son;
ps = dynamic_cast<Tson *>(pf);
ps->f();
cout << ps->data << endl; // 访问子类独有成员有效

/* 下行转换(pf实际指向父类对象):含有不安全操作,dynamic_cast发挥作用返回NULL */
pf = &father;
ps = dynamic_cast<Tson *>(pf);
assert(ps != NULL); // 违背断言,阻止以下不安全操作
ps->f();
cout << ps->data << endl; // 不安全操作,对象实例根本没有data成员

/* 下行转换(pf实际指向父类对象):含有不安全操作,static_cast无视 */
pf = &father;
ps = static_cast<Tson *>(pf);
assert(ps != NULL);
ps->f();
cout << ps->data << endl; // 不安全操作,对象实例根本没有data成员

system("pause");
}
reinterpret_cast

仅重新解释类型,但没有进行二进制的转换:

1)转换的类型必须是一个指针,应用、算术类型、函数指针或者成员指针。

2)在比特级别上进行转换,可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。但不能将非32bit的实例转成指针。
3) 最普通的用途就是在函数指针类型之间进行转换。
4) 很难保证移植性。

什么都可以转,万不得已不要使用,一般前三种转换方式不能解决问题了使用这种强制类型转换方式。

1
2
3
4
5
6
7
8
int main() {
int data = 10;
int *pi = &data;

float *fpi = reinterpret_cast<float *>(pi);

return 0;
}
总结

去const属性用const_cast

基本类型转换用static_cast

多态类之间的类型转换用dynamic_cast

不同类型的指针类型转换用reinterpret_cast

参考链接:

https://mp.weixin.qq.com/s/6YW7VX787X7kZiRBLbVn-Q

https://blog.csdn.net/weixin_44212574/article/details/89043854

https://www.cnblogs.com/evenleee/p/10382335.html