C++中的强制类型转换

一个命名的强制类型转换具有如下形式:
cast-name<type>(expression);
其中,type是转换的目标类型而expression是要转换的值。如果type是引用类型,则结果是左值。cast-name是static_cast,dynamic_cast,const_cast和reinterpret_cast中的一种。

static_cast

任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。比如,通过将一个运算对象强制转换成double类型就能使表达式执行浮点数除法:
double slope = static_cast<double>(j) / i;

当需要把一个较大的算术类型赋值给较小的类型时,static_cast非常有用

此时,强制类型转换告诉程序的读者和编译器:我们知道并且不在乎潜在的精度损失。一般来说,如果编译器发现一个较大的算术类型试图赋值给较小的算术类型,就会给出警告信息;但是当我们执行了显式的类型转换后,警告信息就会被关闭了。

static_cast对于编译器无法自动执行的类型转换也非常有用

例如,可以使用static_cast找回存在于void*指针中的值:

1
2
void *p = &d;
double dp = static_cast<double*>(p); //将void*转换回初始的指针类型

当我们把指针存放在void*中,并且使用static_cast将其强制转换回原来的类型时,应该确保指针的值保持不变。也就是说,强制转换的结果将于原始的地址值相等,因此必须确保转换后所得的类型就是指针所指的类型。类型一旦不符,将产生未定义的后果。

const_cast

const_cast只能改变运算对象底层const:

1
2
const char *pc;
char *p = const_cast<char *>(pc) //正确,但是通过p写值是未定义的行为

对于将常量对象转换成非常量对象的行为,称为去掉const性质。一旦去掉了某个对象的const性质,编译器就不再阻止我们对该对象进行写操作了。如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果。

只有const_cast能改变表达式的常量属性,使用其他形式的命名强制类型转换改变表达式的常量属性都将引发编译器错误。同样,也不能用const_cast改变表达式的类型:

1
2
3
4
const char *cp;
char *q = static_cast<char *>(cp); //错误,static_cast不能转换掉const性质
static_cast<string>(cp); //正确,字符串字面值转换成string类型。
const_cast<string>(cp); //错误,const_cast只改变常量属性

const_cast常常用于有函数重载的上下文中。

reinterpret_cast

reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。

比如:

1
2
int *ip;
char *pc = reinterpret_cast<char *>(ip);

需要清楚pc所指的真实对象是一个int而非字符,如果把pc当成普通字符指针使用就会在运行时引发错误。比如:
string str(pc)
可能会导致异常的运行时行为。

使用reinterpret_cast是非常危险的。用pc初始化str的例子证明了这一点。其中的关键问题是类型改变了,但编译器没有给出任何警告或者错误提示信息。当用一个int地址初始化pc时,由于显式的声称这种转换合法,所以编译器不会发出任何警告或错误信息。接下来再使用pc时就会认定它的值是char* 类型,编译器没法知道它实际存放的是指向int的指针。最终的结果就是,虽然使用pc初始化str没有什么意义,甚至可能引发更糟的后果,但仅从语法上无可挑剔。如果将ip强制转换成pc的语句和用pc初始化string对象的语句分属不同的文件就更是如此。


参考资料:
《C++ primer 5th》