类型转换是一个笼统、模糊的词,对应着C语言类型转换的粗粒度特性。
C++的观点,类型转换之间存在着巨大的不同,例如把一个指向常量const
的指针(pointer-to-const-object,const int* pa = &a,const int* != int* const,前者是指向)转换成指向非常量对象的指针(pointer-to-non-const-object),也即去除常量性,再比如,把一个指向基类的指针转换成指向子类的指针(转换的是对象类型)。
这顺便说一下常量指针
与指针常量
之间的区别:
int a = 10;
int b = 20;
const int* pa = &a; // 常量指针,指向的内容不可改变
*pa = 15; // 错误,此时*pa不是可修改的左值
int* const pa = &a; // 指针常量(类比int常量),指针本身不可发生变话
pa = &b; // 错误,此时pa不是可修改的左值
传统的C-style的类型转换不对上述两种转换(消除常量性,基类指针转换为子类指针)进行区分。
这是C-style风格转换的一种缺陷,它的另外一个问题是其转换形式在程序中难以识别,容易与其他C++语法特性发生混淆。语法上,C-style的类型转换由圆括号和标识符组成,即(type)expression
,这种语法格式可以发生在C++的任何地方,尤其是C++还支持括号运算符重载。
doubel val = 10.;
int a = (int)val;
为了解决C-style类型转换的上述问题,C++引入了C++风格的类型转换操作符:static_cast、const_cast、dynamic_cast,和reinterpret_cast。语法形式上:
(new-type)expression
转换为:
static_cast<new-type>(expression)
例如把一个int
转换成double
,以便让包含int
类型变量的表达式产生浮点数值的结果(也就是纯int
类型的变量如果不经历类型转换,四则运算之后的结果仍是int
类型)。
int numerator, denominator;
// C-style
double result = (double)numerator/denominator;
// C++-style
double result = static_cast<double>(numerator)/denominator;
static_cast功能上基本与C-style的类型转换一样强大,含义也一样,也有转换能力上的限制。例如,不能用static_cast像用C-style的类型转换一样,把struct转换呢为int类型或者把double转换为指针类型。另外static_cast不能去除表达式中的常量性(C-style具备这样的去除常量性),const_cast具备这样的去除常量性。
int a = 10, b = 20;
int* const pa = &a;
pa = &b; // 错误,pa不是可修改的左值
const_cast<int*>(pa) = &b; // 通过
(int*)pa = &b; // c-style也是可以的,但不推荐
const_cast用于类型转换掉表达式的常量性(或者volatileness性)。更重要的一点是,通过使用const_cast,你向人们或编译器传达了通过类型转换想做的仅仅只是消除表达式的常量性,除此之外,没有非分之想。这个讯号被编译器所接受,如果你试图使用const_cast完成消除常量性之外的事,会被编译器拒绝。
class Widget {};
class SpecialWidget: public Widget {};
void update(SpecialWidget* psw){}
SpecialWidget sw; // sw是一个非const变量
const SpecialWidget& csw = sw; // csw是sw的常量引用
update(&csw); // 错误!无法将(const SpecialWidget*)转换为(SpecialWidget* )
update(const_static<SpecialWidget*>(&csw)); // 正确,csw的常量性被转换掉,csw和sw在update函数中都将被更新
update((SpecialWidget*)&csw); // 同上,但用了一个更难识别的C-style类型转换
Widget* pw = new Widget;
update(pw); // 错误!无法将(Widget*)转换为(SpecialWidget*)
update(const_cast<SpecialWidget*>(pw)); // 错误,const_cast仅能用在消除常量性上,不能用在基类向子类进行转换。
dynamic_cast 的类型必须是指向完整类类型(完整类类型(一般为子类)的指针)或void* 类型的指针或引用。
dynamic_cast被用于安全地沿着类的继承关系向下进行类型转换,失败的转换将返回空指针(当对指针进行转换时),或者抛出异常(当对引用进行类型转换时)。
Widget* pw = new Widget;
update(dynamic_cast<SpecialWidget*>(pw));
// 可能有的编译器会提示出错,即运行dynamic_cast的操作数必须包含多态类类类型,即存在virtual函数
// 添加一个虚函数之后,正确。
// 如果pw确实指向一个对象,否则传递过去的将是空指针。
void updateViaRef(SpecialWidget& rsw){}
updateViaRef(dynamic_cast<SpecialWidget&>(*pw));
dynamic_cast在继承关系上进行类型转换是有限制的,它不能用于缺乏虚函数的类型,也不能用来转换掉constness。
int numerator, denominator;
double result = dynamic_cast<double>(numerator)/denominator;
// 错误!不存在继承关系
const Widget cw;
update(dynamic_cast<SpecialWidget*>(&cw));
// 错误,dynamic_cast不能消除常量性
update(const_cast<SpecialWidget*>(dynamic_cast<SpecialWidget*>(&cw)));
// 错误,dynamic_cast不能转换一个被const修饰的表达式
update(dynamic_cast<SpecialWidget*>(const_cast<Widget*>(&cw)));
// 通过,所以正确顺序是先消除常量性,再沿着继承体系向下转换
使用reinterpret_cast进行的转换,其转换结果几乎都是执行期语义(implementation-defined),使用reinterpret_cast的代码很难移植。
reinterpret_cast最普通的用途是在函数指针类型之间进行转换,例如,假设你有一个函数指针数组,
typedef void(*FuncPtr)();
FuncPtr funcPtrArray[10];
如果我们希望把一个指向下面函数声明的指针存入funcPtrArray数组:
int doSomething();
它们的返回类型不同,
funcPtrArray[0] = &doSomething; // 错误,类型不匹配
funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething);
// 通过,reinterpret_cast迫使编译器以你的方法看待它们
转换函数指针的代码不可移植,慎用。
[1] More Effective C++