More Effective C++:范例转换
副标题#e#
仔细想想职位猥贱的范例转换成果(cast),其在措施设计中的职位就象goto语句一样令人藐视。可是它还不是无执法人忍受,因为当在某些紧急的关头,范例转换照旧必须的,这时它是一个必须品。
不外C气势气魄的范例转换并不代表所有的范例转换成果。一来它们过于卤莽,能答允你在任何范例之间举办转换。不外假如要举办更准确的范例转换,这会是一个利益。在这些范例转换中存在着庞大的差异,譬喻把一个指向const工具的指针(pointer-to-const-object)转换成指向非const工具的指针(pointer-to-non-const-object)(即一个仅仅去除cosnt的范例转换),把一个指向基类的指针转换成指向子类的指针(即完全改变工具范例)。传统的C气势气魄的范例转换差池上述两种转换举办区分。(这一点也不令人惊奇,因为C气势气魄的范例转换是为C语言设计的,而不是为C++语言设计的)。
二来C气势气魄的范例转换在措施语句中难以识别。在语法上范例转换由圆括号和标识符构成,而这些可以用在C++中的任那里所。这使得答复象这样一个最根基的有关范例转换的问题变得很坚苦,“在这个措施中是否利用了范例转换?”。这是因为人工阅读很大概忽略了范例转换的语句,而操作象grep的东西措施也不能从语句组成上区分出它们来。
C++通过引进四个新的范例转换操纵符降服了C气势气魄范例转换的缺点,这四个操纵符是,static_cast, const_cast, dynamic_cast, 和reinterpret_cast。在大大都环境下,对付这些操纵符你只需要知道本来你习惯于这样写,(type) expression而此刻你总应该这样写: static_cast(expression);譬喻,假设你想把一个int转换成double,以便让包括int范例变量的表达式发生出浮点数值的功效。假如用C气势气魄的范例转换,你能这样写:
#p#副标题#e#
int firstNumber, secondNumber;
...
double result = ((double)firstNumber)/secondNumber;
假如用上述新的范例转换要领,你应该这样写:
double result = static_cast(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属性之外的工作,你的范例转换将被拒绝。下面是一些例子:
class Widget { ... };
class SpecialWidget: public Widget { ... };
void update(SpecialWidget *psw);
SpecialWidget sw; // sw 是一个非const 工具。
const SpecialWidget& csw = sw; // csw 是sw的一个引用
// 它是一个const 工具
update(&csw); // 错误!不能通报一个const SpecialWidget* 变量
// 给一个处理惩罚SpecialWidget*范例变量的函数
update(const_cast(&csw));
// 正确,csw的const被显示地转换掉(
// csw和sw两个变量值在update
//函数中能被更新)
update((SpecialWidget*)&csw);
// 同上,但用了一个更难识别
//的C气势气魄的范例转换
Widget *pw = new SpecialWidget;
update(pw); // 错误!pw的范例是Widget*,可是
// update函数处理惩罚的是SpecialWidget*范例
update(const_cast(pw));
// 错误!const_cast仅能被用在影响
// constness or volatileness的处所上。,
// 不能用在向担任子类举办范例转换。
到今朝为止,const_cast最普通的用途就是转换掉工具的const属性。
第二种非凡的范例转换符是dynamic_cast,它被用于安详地沿着类的担任干系向下举办范例转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,并且你能知道转换是否乐成。失败的转换将返回空指针(当对指针举办范例转换时)可能抛出异常(当对引用举办范例转换时):
Widget *pw;
...
update(dynamic_cast(pw));
// 正确,通报给update函数一个指针
// 是指向变量范例为SpecialWidget的pw的指针
// 假如pw确实指向一个工具,
// 不然通报已往的将使空指针。
void updateViaRef(SpecialWidget& rsw);
updateViaRef(dynamic_cast(*pw));
//正确。 通报给updateViaRef函数
// SpecialWidget pw 指针,假如pw
// 确实指向了某个工具
// 不然将抛出异常
#p#分页标题#e#
dynamic_casts在辅佐你欣赏担任条理上是有限制的。它不能被用于缺乏虚函数的范例上,也不能用它来转换掉constness:
int firstNumber, secondNumber;
...
...
double result = dynamic_cast(firstNumber)/secondNumber;
// 错误!没有担任干系
const SpecialWidget sw;
...
update(dynamic_cast(&sw));
// 错误! dynamic_cast不能转换
// 掉const。
如你想在没有担任干系的范例中举办转换,你大概想到static_cast。假如是为了去除const,你总得用const_cast。
这四个范例转换符中的最后一个是reinterpret_cast。这个操纵符被用于的范例转换的转换功效险些都是实现时界说(implementation-defined)。因此,利用reinterpret_casts的代码很难移植。
reinterpret_casts的最普通的用途就是在函数指针范例之间举办转换。譬喻,假设你有一个函数指针数组:
typedef void (*FuncPtr)(); // FuncPtr is 一个指向函数
// 的指针,该函数没有参数
// 也返回值范例为void
FuncPtr funcPtrArray[10]; // funcPtrArray 是一个能容纳
// 10个FuncPtrs指针的数组
让我们假设你但愿(因为某些莫名其妙的原因)把一个指向下面函数的指针存入funcPtrArray数组:
int doSomething();
你不能不颠末范例转换而直接去做,因为doSomething函数对付funcPtrArray数组来说有一个错误的范例。在FuncPtrArray数组里的函数返回值是void范例,而doSomething函数返回值是int范例。
funcPtrArray[0] = &doSomething; // 错误!范例不匹配
reinterpret_cast可以让你迫使编译器以你的要领去对待它们:
funcPtrArray[0] = // this compiles
reinterpret_cast(&doSomething);
转换函数指针的代码是不行移植的(C++不担保所有的函数指针都被用一样的要领暗示),在一些环境下这样的转换会发生不正确的功效(拜见条款31),所以你应该制止转换函数指针范例,除非你处于着背水一战和尖刀架喉的危急时刻。一把尖利的刀。一把很是尖利的刀。
假如你利用的编译器缺乏对新的范例转换方法的支持,你可以用传统的范例转换要领取代static_cast, const_cast, and reinterpret_cast。也可以用下面的宏替换来模仿新的范例转换语法:
#define static_cast(TYPE,EXPR) ((TYPE)(EXPR))
#define const_cast(TYPE,EXPR) ((TYPE)(EXPR))
#define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))
你可以象这样利用利用:
double result = static_cast(double, firstNumber)/secondNumber;
update(const_cast(SpecialWidget*, &sw));
funcPtrArray[0] = reinterpret_cast(FuncPtr, &doSomething);
这些模仿不会象真实的操纵符一样安详,可是当你的编译器可以支持新的的范例转换时它们可以简化你把代码进级的进程。
没有一个容易的要领来模仿dynamic_cast的操纵,可是许多函数库提供了函数,安详地在派生类与基类之间的举办范例转换。假如你没有这些函数而你有必需举办这样的范例转换,你也可以回到C气势气魄的范例转换要领上,可是这样的话你将不能获知范例转换是否失败。虽然,你也可以界说一个宏来模仿dynamic_cast的成果,就象模仿其它的范例转换一样:
#define dynamic_cast(TYPE,EXPR) (TYPE)(EXPR)
请记着,这个模仿并不能完全实现dynamic_cast的成果,它没有步伐知道转换是否失败。
我知道,是的,我知道,新的范例转换操纵符不是很雅观并且用键盘键入也很贫苦。假如你发明它们看上去实在令人讨厌,C气势气魄的范例转换还可以继承利用而且正当。然而正是因为新的范例转换符缺乏美感才气使它补充了在寄义准确性和可辨认性上的缺点,而且利用新范例转换符的措施更容易被理会(岂论是对人工照旧对付东西措施),它们答允编译器检测出本来不能发明的错误。这些都是放弃C气势气魄范例转换要领的强有力的来由,尚有第三个来由:也许让范例转换符不雅观和键入贫苦是一件功德。