显式类型转换
有时我们需要处理“原始的存储”,也就是那种保存或者将要保存某种对象的存储,而编译器并不知道对象的类型。例如,一个存储分配程序可能返回一个新分配存储块的void*指针,或者我们希望说明应该把某个整数当做一个I/O设备的地址等:
void* malloc(size_t);
void f()
{
int* p = static_cast<int*>(malloc(100)); // 新分配的存储,用做一些整数
IO_device* dl = reinterpret_cast<IO_device*>(0xff00); // 位于0xff00的设备
}
编译器并不知道由void*所指的对象的类型,它也不可能知道0xff00是否为合法的地址。因此,这些转换的正确性完全掌握在程序员的手里。显式的类型转换常常被称做强制,这只是很少的情况下是绝对必要的东西。然而,在传统上它却被过度使用,并成为一个主要的错误根源。
static_cast运算符完成相关类型之间的转换,例如在同一个类层次结构中的一个指针类型到另一个指针类型,整型到枚举类型,或者浮点类型到整型等。
reinterpret_cast处理互不相关类型之间的转换,例如从整型到指针,或者从一个指针到另一个毫不相干的指针类型。这种区分使编译器能对static_cast做某种小的类型检查,并使程序员能看清由reinterpret_cast的存在所表明的更危险的类型转换。有些static_cast是可移植的,但极少reinterpret_cast能是这样。对reinterpret_cast很难做出任何保证。但一般而言,它提供了一个新类型的值,并保持其参数原来的二进制模式。如果目标类型具有至少与原值同样多的二进制位,我们就可以将结果再通过reinterpret_cast,转回到原来的类型并使用它。只是在被使用的类型正好就是所定义值原来的类型时,才能保证reinterpret_cast的结果是可以用的。
如果你感到要去使用显式的类型转换,请花一点时间考虑它是否确实有必要。对于C必须用(1.6节)。或者在C++的早期版本里需要用(1.6.2节、B.2.3节)的显式类型转换中的大部分情况,在C++里都不再需要了。在许多程序里完全可以避免显式的类型转换;在另一些程序里,其使用也可以局部到少数例行程序内部。在这本书里,在实际情况中出现显式类型转换的只有6.2.7节、7.7节、13.5节、13.6节、17.6.2.3节、15.4节、25.4.1节和E.3.1节。
另外还提供了一种运行中检查的转换形式dynamic_cast(15.4.1节),还有一种清除const和volatile限定符的转换形式const_cast(15.4.2.1节)。
C++从C那里继承来(T)e记法,这种形式可以执行能用static_cast、reinterpret_cast和const_cast的组合表述的任何转换,它从表达式e出发去做出一个T类型的值(B.2.3节)。这种C风格的强制远比所有上述的命名转换更加危险,因为在大的程序里这种记法极难看清楚,也因为程序员并没有将转换的意图明确表示出来。也就是说,(T)e可能做相关类型间的可移植转换,无关类型间的不可移植转换,或者去掉指针类型的const修饰符。如果不知道T和e的准确类型,你什么都说不清楚。
🔚