C++中常用的类型转换方式有reinterpret_cast、static_cast、dynamic_cast,下面介绍他们的作用和使用场景。
目录
1、reinterpret_cast
1.1、含义
reinterpret翻译过来是“重新解释”,字面上的意思就是将对象重新解释为另一个类,也就是强制转换,实际上它的作用也是如此。
1.2、示例
例:
现有A类和B类。
#include <iostream>
using namespace std;
class A
{
public:
int data { 100 };
};
class B
{
public:
char data[4] { 0 };
};
我们用reinterpret_cast来转换,为了演示效果更好,准备一个数字6666,它的十六进制是1A0A。
int main()
{
B *b = new B;
A *a = reinterpret_cast<A*>(b);
cout << a->data << endl;
b->data[0] = 0x0A;
b->data[1] = 0x1A;
cout << a->data << endl;
}
可以看到,reinterpret_cast是直接将b对象的那块内存当作A类型来解释了。
1.3、存在的问题
上一节说到reinterpret_cast是将一个对象的内存当作另一个类型来解释,那么在C++中就会存在一些风险,常见风险如下:
- 访问指针时可能会导致程序崩溃。以上述的例子来说,如果A类中的data是一个指针,而b对象的data存放的并不是指针,那么就可能出现访问野指针的情况。
- 调用虚函数时可能导致程序崩溃。我们知道,C++的虚函数也是通过函数指针来实现的,如果A和B的虚函数数量不一致,那在调用虚函数时,也有可能会访问到野指针。
- 数据错乱。这一点在上一节的例子中也有所体现了。
1.4、应用场景
reinterpret_cast一般用在待强转的对象是确定的且可被强转的情况,比如上面的例子,我们清楚B的储存结构是安全的,而且b来自于new B,而不是经过其它函数传参过来的。
具体应用有:同一父类下的子类互转,结构体互转。
2、static_cast
2.1、作用
static_cast也是一种类型转换的方式,它与reinterpret_cast的不同之处在于,它在编译的时候会查错,也就是如果类型转换有问题,我们写代码的时候会提示。
例如:
2.2、存在的问题
虽然static_cast会查错,但也还是存在着风险,比如以下例子:
类型B有虚函数表,而A没有,显然这个程序运行会崩溃,但static_cast并没有报错,这是因为static_cast转换时只会检查它们时候有继承关系,如果有,那就认为是合法的。
static_cast查错的逻辑可以参考下图,我们创建了一个Grand类型对象来转换为其它类型,绿线为合法,红线为非法。
2.3、应用场景
从上一节可以看到,static_cast适用于一些继承关系比较简单的场景,如果继承关系比较简单,用static是比较安全的。
3、dynamic_cast
3.1、作用
dynamic_cast是static_cast的进阶版,它不仅在编译时会查错,在程序运行时也会查错。
它在编译时查错和static_cast有一些区别,需要分两种情况来介绍。
- 对象无虚函数表。此时dynamic_cast会比较严格地检查继承关系是否合法,如下图所示,弥补了static_cast的不足。
- 对象有虚函数表。此时编译器不会查错。
我们可以看到,如果对象有虚函数表,dynamic_cast的查错就会在编译阶段失效,这时候就需要用到它的运行时查错。
当转换不合法时,dynamic_cast会返回NULL,还是刚刚的例子。
这两种不合法的转换返回了NULL。
dynamic_cast运行时查错的逻辑可以参考下图,绿线表示正常转换,红线表示返回NULL。
3.2、存在的问题
dynamic_cast因为在运行时会查错,所以效率比前两者慢,不过性能损耗不大,这个可以视具体需求而定。
3.3、应用场景
dynamic_cast是一种安全度比较高的转换方式,可以适应于大部分情况,我们使用的时候可以用这种格式,要注意异常情况的处理。
A *a = new A;
B *b = dynamic_cast<B*>(a);
if (b != NULL) {
// dosomething
} else {
// exception handle
}