More Effective C++ 学习笔记(2)
2011-09-03 09:53 Daniel Zheng 阅读(458) 评论(0) 编辑 收藏 举报尽量使用C++风格的类型转换
为什么不要使用C风格的类型转换?
C风格的类型转换并不代表所有的类型转换功能。
一来它们过于粗鲁,能允许你在任何类型之间进行转换。不过如果要进行更精确的类型转换,这会是一个优点。在这些类型转换中存在着巨大的不同,例如把一个指向const对象的指针(pointer-to-const-object)转换成指向非const对象的指针(pointer-to-non-const-object)(即一个仅仅去除const的类型转换),把一个指向基类的指针转换成指向子类的指针(即完全改变对象类型)。传统的C风格的类型转换不对上述两种转换进行区分。
二来C风格的类型转换在程序语句中难以识别。在语法上,类型转换由圆括号和标识符组成,而这些可以用在C++中的任何地方。这使得回答象这样一个最基本的有关类型转换的问题变得很困难:“在这个程序中是否使用了类型转换?”。这是因为人工阅读很可能忽略了类型转换的语句,而利用象grep的工具程序也不能从语句构成上区分出它们来。
C++通过引进四个新的类型转换操作符克服了C风格类型转换的缺点,这四个操作符是, static_cast, const_cast, dynamic_cast, 和reinterpret_cast。在大多数情况下,对于这些操作符你只需要知道原来你习惯于这样写,
(type) expression
而现在你总应该这样写:
static_cast<type>(expression)
例如,假设你想把一个int转换成double,以便让包含int类型变量的表达式产生出浮点数值的结果。如果用C风格的类型转换,你能这样写:
int firstNumber, secondNumber;
...
double result = ((double)firstNumber)/secondNumber;
如果用上述新的类型转换方法,你应该这样写:
double result = static_cast<double>(firstNumber)/secondNumber;
static_cast在功能上基本上与C风格的类型转换一样强大,含义也一样。它也有功能上限制。例如,你不能用static_cast象用C风格的类型转换一样把struct转换成int类型或者把double类型转换成指针类型,另外,static_cast不能从表达式中去除const属性,因为另一个新的类型转换操作符const_cast有这样的功能。
其它新的C++类型转换操作符被用在需要更多限制的地方。const_cast用于类型转换掉表达式的const或volatileness属性。通过使用const_cast,你向人们和编译器强调你通过类型转换想做的只是改变一些东西的constness或者 volatileness属性。这个含义被编译器所约束。如果你试图使用const_cast来完成修改constness 或者volatileness属性之外的事情,你的类型转换将被拒绝。下面是一些例子:
1 class Widget { ... };
2 class SpecialWidget: public Widget { ... };
3 void update(SpecialWidget *psw);
4 SpecialWidget sw; // sw 是一个非const 对象。
5 const SpecialWidget& csw = sw; // csw 是sw的一个引用,它是一个const 对象
6 update(&csw); // 错误!不能传递一个const SpecialWidget* 变量,给一个处理SpecialWidget*类型变量的函数
7 update(const_cast<SpecialWidget*>(&csw)); //正确,csw的const被显示地转换掉(csw和sw两个变量值在update函数中能被更新)
8 update((SpecialWidget*)&csw); // 同上,但用了一个更难识别的C风格的类型转换
9 Widget *pw = new SpecialWidget;
10 update(pw); // 错误!pw的类型是Widget*,但是update函数处理的是SpecialWidget*类型
11 update(const_cast<SpecialWidget*>(pw)); // 错误!const_cast仅能被用在影响constness or volatileness的地方上。不能用在向继承子类进行类型转换。
到目前为止,const_cast最普通的用途就是转换掉对象的const属性。
第二种特殊的类型转换符是dynamic_cast,它被用于安全地沿着类的继承关系向下进行类型转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。失败的转换将返回空指针(当对指针进行类型转换时)或者抛出异常(当对引用进行类型转换时)。
1 Widget *pw;
2 ...
3 update(dynamic_cast<SpecialWidget*>(pw));
4 // 正确,传递给update函数一个指针
5 // 是指向变量类型为SpecialWidget的pw的指针
6 // 如果pw确实指向一个对象,
7 // 否则传递过去的将使空指针。
8 void updateViaRef(SpecialWidget& rsw);
9 updateViaRef(dynamic_cast<SpecialWidget&>(*pw));
10 //正确。 传递给updateViaRef函数
11 // SpecialWidget pw 指针,如果pw
12 // 确实指向了某个对象
13 // 否则将抛出异常
dynamic_casts在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上,也不能用它来转换掉constness:
1 int firstNumber, secondNumber;
2 ...
3 double result = dynamic_cast<double>(firstNumber)/secondNumber;
4 // 错误!没有继承关系
5 const SpecialWidget sw;
6 ...
7 update(dynamic_cast<SpecialWidget*>(&sw));
8 // 错误! dynamic_cast不能转换
9 // 掉const。
这四个类型转换符中的最后一个是reinterpret_cast。使用这个操作符的类型转换,其的转换结果几乎都是执行期定义(implementation-defined)。因此,使用reinterpret_casts的代码很难移植。
reinterpret_casts的最普通的用途就是在函数指针类型之间进行转换。例如,假设你有一个函数指针数组:
1 typedef void (*FuncPtr)(); // FuncPtr is 一个指向函数
2
3 // 的指针,该函数没有参数
4
5 // 返回值类型为void
6
7 FuncPtr funcPtrArray[10]; // funcPtrArray 是一个能容纳
8
9 // 10个FuncPtrs指针的数组
让我们假设你希望(因为某些莫名其妙的原因)把一个指向下面函数的指针存入funcPtrArray数组:
1 int doSomething();
你不能不经过类型转换而直接去做,因为doSomething函数对于funcPtrArray数组来说有一个错误的类型。在FuncPtrArray数组里的函数返回值是void类型,而doSomething函数返回值是int类型。
1 funcPtrArray[0] = &doSomething; // 错误!类型不匹配
reinterpret_cast可以让你迫使编译器以你的方法去看待它们:
1 funcPtrArray[0] = // this compiles
2 reinterpret_cast<FuncPtr>(&doSomething);
转换函数指针的代码是不可移植的(C++不保证所有的函数指针都被用一样的方法表示),在一些情况下这样的转换会产生不正确的结果,所以你应该避免转换函数指针类型,除非你处于着背水一战和尖刀架喉的危急时刻。一把锋利的刀。一把非常锋利的刀。