C++箴言:将强制转型减到最少
2008-03-08 21:33:39 来源:WEB开发网 闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌熼梻瀵割槮缁炬儳缍婇弻鐔兼⒒鐎靛壊妲紒鐐劤缂嶅﹪寮婚悢鍏尖拻閻庨潧澹婂Σ顔剧磼閻愵剙鍔ょ紓宥咃躬瀵鎮㈤崗灏栨嫽闁诲酣娼ф竟濠偽i鍓х<闁绘劦鍓欓崝銈囩磽瀹ュ拑韬€殿喖顭烽幃銏ゅ礂鐏忔牗瀚介梺璇查叄濞佳勭珶婵犲伣锝夘敊閸撗咃紲闂佺粯鍔﹂崜娆撳礉閵堝洨纾界€广儱鎷戦煬顒傗偓娈垮枛椤兘骞冮姀銈呯閻忓繑鐗楃€氫粙姊虹拠鏌ュ弰婵炰匠鍕彾濠电姴浼i敐澶樻晩闁告挆鍜冪床闂備胶绮崝锕傚礈濞嗘挸绀夐柕鍫濇川绾剧晫鈧箍鍎遍幏鎴︾叕椤掑倵鍋撳▓鍨灈妞ゎ厾鍏橀獮鍐閵堝懐顦ч柣蹇撶箲閻楁鈧矮绮欏铏规嫚閺屻儱寮板┑鐐板尃閸曨厾褰炬繝鐢靛Т娴硷綁鏁愭径妯绘櫓闂佸憡鎸嗛崪鍐簥闂傚倷娴囬鏍垂鎼淬劌绀冮柨婵嗘閻﹂亶姊婚崒娆掑厡妞ゃ垹锕ら埢宥夊即閵忕姷顔夐梺鎼炲労閸撴瑩鎮橀幎鑺ョ厸闁告劑鍔庢晶鏇犵磼閳ь剟宕橀埞澶哥盎闂婎偄娲ゅù鐑剿囬敃鈧湁婵犲﹤鐗忛悾娲煛鐏炶濡奸柍瑙勫灴瀹曞崬鈻庤箛鎾寸槗缂傚倸鍊烽梽宥夊礉鎼达絽鍨濇い鏍仜妗呴梺鍛婃处閸ㄦ壆绮婚幎鑺ュ€甸柨婵嗙凹缁ㄨ棄霉閻樿崵鐣烘慨濠冩そ濡啫鈽夊▎鎰€烽梺璇插閻噣宕¢幎鑺ュ仒妞ゆ洍鍋撶€规洖鐖奸、妤佸緞鐎n偅鐝┑鐘愁問閸n垳寰婇崜褉鍋撶粭娑樻搐缁犳煡鏌涢妷顔煎闁藉啰鍠栭弻锝夊棘閹稿孩鍠愰梺鑽ゅ枎缂嶅﹪寮诲☉鈶┾偓锕傚箣濠靛洨浜俊鐐€ら崜娆撴偋閸℃稈鈧棃宕橀鍢壯囧箹缁厜鍋撻懠顒€鍤紓鍌氬€风欢锟犲窗濡ゅ懎绠伴柟闂寸劍閸嬧晠鏌i幋锝嗩棄缁绢厸鍋撻梻浣虹帛閸旀洜绮旈棃娴虫盯宕橀鍏兼К闂侀€炲苯澧柕鍥у楠炴帡骞嬪┑鎰磻闁诲氦顫夐幐椋庣矆娓氣偓閸╃偤骞嬮敂钘変汗闂佸湱绮敮鈺傚閳ь剛绱撴担鐟板姢鐟滄壆鍋熼崚鎺戔枎閹惧疇鎽曞┑鐐村灦閻喖鈻介鍫熺厵閻熸瑥瀚慨鍥ㄣ亜閵夛妇绠炴慨濠冩そ閺屽懘鎮欓懠璺侯伃婵犫拃鍌氬祮闁哄瞼鍠栭幖褰掝敃閿濆懐锛撻梻浣瑰缁诲嫰宕戝☉銏犵厴闁瑰濮崑鎾绘晲鎼存ê浜炬い鎾寸⊕濞呭﹪鏌$仦鐣屝f繛纰变邯楠炲繒浠﹂挊澶婅厫闂傚倷鐒﹂惇褰掑磹閺囥垹绠犻柟閭﹀枟椤洟鏌熼幆褏鎽犲┑顖涙尦閺屾盯骞橀弶鎴犵シ闂佸憡鎸稿畷顒勨€旈崘顔嘉ч柛鈩冾殘娴犳悂姊洪懡銈呮毐闁哄懏鐩幃楣冩倻閽樺)銊ф喐婢舵劕纾婚柟鍓х帛閺呮煡骞栫划鐟板⒉闁诲繐绉瑰铏圭磼濡闉嶅┑鐐插级閿曘垺淇婇悽绋跨妞ゆ牗姘ㄩ悿鈧梻鍌氬€搁悧濠勭矙閹邦喛濮抽柤娴嬫櫇绾捐棄霉閿濆牊顥夐柣鎾村姈閹便劌螣缁嬪灝顬嬪┑鈥冲级閸旀瑩鐛Ο鍏煎珰闁肩⒈鍓﹀Σ浼存⒒娴gǹ鏆遍柟纰卞亰瀹曟劖绻濆В绋挎喘瀵埖鎯旈幘瀛樻澑婵$偑鍊栧濠氬Υ鐎n亶鍟呴柕澶涜礋娴滄粍銇勯幘璺轰粶婵℃彃顭烽弻锝夋晲閸パ冨箣濡ょ姷鍋炵敮锟犵嵁鐎n喖绫嶉柍褜鍓熼幃妤佺節濮橆厸鎷洪柣鐔哥懃鐎氼參宕曞Δ鍛厱婵☆垵銆€閸嬫捇鎮㈤幓鎺戠阀濠电姷鏁告慨鐑藉极閸涘﹥鍙忛柣鎴f閺嬩線鏌涘☉姗堟敾闁告瑥绻戦妵鍕箻閸楃偟浠肩紒鐐劤椤兘寮婚悢鐓庣鐟滃繒鏁☉銏$厓闂佸灝顑呴悘锕傛煏閸パ冾伃妤犵偞甯″畷鍗烆渻閹屾缂傚倸鍊搁崐椋庣矆娓氣偓钘濋梺顒€绉撮弸浣糕攽閻樿櫕鐨戠€规挷绶氶弻娑㈠焺閸愵亖濮囬梺绋匡功閸忔﹢寮诲☉妯锋斀闁糕剝顨忔导鈧俊鐐€栧褰掑礉閺囥垹鐓橀柟杈鹃檮閸婂鏌涢妷銏℃珖閺嶏繝姊绘担鍛婂暈闁圭ǹ顭烽幃鐑芥晜閻e备鏀虫繝鐢靛Т濞诧箓宕甸崘顔界厓闁告繂瀚弳鐔兼煥濞戞瑧鐭掓慨濠囩細閵囨劙骞掗幋婊冩瀳闂備礁鎲¢悷銉︻殽閹间礁鐓濋柟鐐灱閸亪鏌涢銈呮灁闁告ɑ鎮傞弻锝堢疀閺囩偘鎴风紒缁㈠幖閻栫厧鐣烽幋锕€绠婚悹鍥皺閻も偓濠电偠鎻徊浠嬪箟閿熺姴纾规い鏍仦閳锋垹鐥鐐村櫣濞存粌缍婇幃璺衡槈閺嵮冨Е闂佺硶鏂侀崑鎾愁渻閵堝棗绗掗柛鐕佸亰閹啫煤椤忓懐鍘告繛杈剧到濠€杈ㄦ櫠椤忓牊鐓冮悷娆忓閻忔挳鏌熼鐣屾噰鐎殿喖鐖奸獮瀣偐鏉堫煈鏁囬梻鍌氬€风粈浣革耿鏉堛劎浠氶梻浣侯攰婵倗鍒掓惔銊ョ闁圭儤顨呯猾宥夋煕椤愩倕鏋庡ù鐘烘缁辨挻鎷呴崜鎻掑壍濡炪倖娲樻繛濠囧极閸愵喖纾兼繛鎴炶壘楠炲牓姊绘担鍛婃儓婵炲眰鍨藉畷婵嗙暆閸曨剙鈧爼鏌eΟ鑲╁笡闁绘挻娲熼弻鐔兼嚋椤掆偓婵$厧霉濠婂嫬鍔ら柍瑙勫灴閺佸秹宕熼鈩冩線闂備胶枪閿曘儵鎮ч悩鑼殾婵犻潧顑嗛弲婵嬫煃瑜滈崜鐔煎灳閿曞倸閿ゆ俊銈傚亾闁绘帒鐏氶妵鍕箳瀹ュ牆鍘$紓浣哄Т婢т粙鍩€椤掆偓閸樻粓宕戦幘鏂ユ斀闁绘ǹ浜粣鏃堟煕鐎n偒娈旈柍瑙勫灴椤㈡瑧娑甸悜鐣屽弽婵犵數鍋涢幏鎴犲緤閸啣锝夊箛閺夎法顔婇梺鐟板暱绾绢參宕伴幘璇茬闁绘ḿ绮崵鎴︽煠缁嬭法浠涙慨锝嗗姍濮婂宕掑顑藉亾閻戣姤鍤勯柤鍝ユ暩娴犳碍绻濋悽闈涗粶妞ゆ洦鍙冨畷妤€螣娓氼垰娈ㄥ銈嗗姂閸婃牜鈧碍姘ㄩ埀顒傛嚀婢瑰﹪宕伴弽褉鏋旈柕濠忓缁♀偓闂佹眹鍨藉ḿ褎鐗庣紓浣哄亾濠㈡ḿ绮旈悷閭﹀殨闁哄被鍎辩粻鐢告煙閻戞ḿ绠橀柛鐐垫暬閺岋綁鎮╅悜姗嗕哗闁诲繐绻堥崝宀勵敊韫囨稑唯鐟滃宕戦幘鑸靛枂闁告洦鍓欑喊宥呪攽閳藉棗浜濈紒璇插€块敐鐐剁疀濞戞瑦鍎梺闈╁瘜閸橀箖鏁嶅⿰鍐f斀闁宠棄妫楅悘鐘绘煙绾板崬浜伴柨婵堝仜椤撳ジ宕堕埡鍐跨闯濠电偠鎻紞渚€藟閹捐绀夌€广儱顦伴悡娆戠磼鐎n亞浠㈤柡鍡涗憾閺岋綁鏁愰崶褍骞嬪Δ鐘靛仜椤戝寮崘顔肩劦妞ゆ帒鍊绘稉宥呪攽閻樺磭顣查柛瀣剁秮閺屾盯濡烽幋婵嗘殶濡ょ姴娲幃妤冩喆閸曨剙纰嶇紓浣割槹閹告娊鍨鹃弮鍫濈妞ゆ柨妲堣閺屾盯鍩勯崗鐙€浜Λ鍕吋閸モ晝锛濇繛杈剧到婢瑰﹪宕曢幇鐗堢厱闁靛ǹ鍎遍。宕囩磼椤旂⒈鍎忔い鎾冲悑瀵板嫮鈧綆浜栭崑鎾绘煥鐎c劋绨婚梺鐟版惈缁夊爼藝閿旈敮鍋撳▓鍨灈闁诲繑绻堥崺鐐哄箣閿曗偓閻擄繝鏌涢埄鍐炬畼濞寸媭鍨跺娲川婵犲海鍔堕梺鍛婃处閸欏骸煤閸涘﹣绻嗛柕鍫濈箳閸掍即鏌涢悤浣哥仸鐎规洘鍔欏畷褰掝敃閿濆懎浼庢繝纰樻閸ㄦ娊宕㈣缁傚秵銈i崘鈺佲偓鍨叏濡厧浜鹃悗姘炬嫹

核心提示:C++ 的规则设计为保证不会发生类型错误,在理论上,C++箴言:将强制转型减到最少,假如你的程序想顺利地通过编译,你就不应该试图对任何对象做任何不安全的或无意义的操作,它们更轻易被注重到,而且他们做的事情也更加明确,这是一个非常有价值的保证,你不应该轻易地放弃它
C++ 的规则设计为保证不会发生类型错误。在理论上,假如你的程序想顺利地通过编译,你就不应该试图对任何对象做任何不安全的或无意义的操作。这是一个非常有价值的保证,你不应该轻易地放弃它。
我们就从回顾强制转型的语法开始,因为对于同样的强制转型通常有三种不同的写法。C 风格(C-style)强制转型如下:
(T) eXPRession // cast expression to be of type T
函数风格(Function-style)强制转型使用这样的语法:
T(expression) // cast expression to be of type T
这两种形式之间没有本质上的不同,它纯粹就是一个把括号放在哪的问题。我把这两种形式称为旧风格(old-style)的强制转型。
C++ 同时提供了四种新的强制转型形式(通常称为新风格的或 C++ 风格的强制转型):
const_cast
dynamic_cast
reinterpret_cast
static_cast
每一种适用于特定的目的:
·const_cast 一般用于强制消除对象的常量性。它是唯一能做到这一点的 C++ 风格的强制转型。
·dynamic_cast 主要用于执行“安全的向下转型(safe downcasting)”,也就是说,要确定一个对象是否是一个继续体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型。也是唯一可能有重大运行时代价的强制转型。(过一会儿我再提供细节。)
·reinterpret_cast 是特意用于底层的强制转型,导致实现依靠(implementation-dependent)(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见。在本书中我只用了一次,而且还仅仅是在讨论你应该如何为裸内存(raw memory)写一个调谐分配者(debugging allocator)的时候。
·static_cast 可以被用于强制隐型转换(例如,non-const 对象转型为 const 对象(就像 Item 3 中的),int 转型为 double,等等)。它还可以用于很多这样的转换的反向转换(例如,void* 指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个 const 对象转型为 non-const 对象。(只有 const_cast 能做到。)
旧风格的强制转型依然合法,但是新的形式更可取。首先,在代码中它们更轻易识别(无论是人还是像 grep 这样的工具都是如此),这样就简化了在代码中寻找类型系统被破坏的地方的过程。第二,更精确地指定每一个强制转型的目的,使得编译器诊断使用错误成为可能。例如,假如你试图使用一个 const_cast 以外的新风格强制转型来消除常量性,你的代码将无法编译。
当我要调用一个 explicit 构造函数用来传递一个对象给一个函数的时候,大概就是我仅有的使用旧风格的强制转换的时候。例如:
class Widget {
public:
explicit Widget(int size);
...
};
void doSomeWork(const Widget& w);
doSomeWork(Widget(15)); // create Widget from int
// with function-style cast
doSomeWork(static_cast
// with C++-style cast

int x, y;
...
double d = static_cast
// floating point division
int x 到 double 的强制转型理所当然要生成代码,因为在大多数系统架构中,一个 int 的底层表示与 double 的不同。这可能还不怎么令人吃惊,但是下面这个例子可能会让你稍微开一下眼:
class Base { ... };
class Derived: public Base { ... };
Derived d;
Base *pb = &d; // implicitly convert Derived* → Base*
这里我们只是创建了一个指向派生类对象的基类指针,但是有时候,这两个指针的值并不相同。在当前情况下,会在运行时在 Derived* 指针上应用一个偏移量以得到正确的 Base* 指针值。
这后一个例子表明一个单一的对象(例如,一个类型为 Derived 的对象)可能会有不止一个地址(例如,它的被一个 Base* 指针指向的地址和它的被一个 Derived* 指针指向的地址)。这在 C 中就不会发生,也不会在 Java 中发生,也不会在 C# 中发生,它仅在 C++ 中发生。实际上,假如使用了多继续,则一定会发生,但是在单继续下也会发生。与其它事情合在一起,就意味着你应该总是避免对 C++ 如何摆放事物做出假设,你当然也不应该基于这样的假设执行强制转型。例如,将一个对象的地址强制转型为 char* 指针,然后对其使用指针运算,这几乎总是会导致未定义行为。
但是请注重我说一个偏移量是“有时”被需要。对象摆放的方法和他们的地址的计算方法在不同的编译器之间有所变化。这就意味着仅仅因为你的“我知道事物是如何摆放的”而使得强制转型能工作在一个平台上,并不意味着它们也能在其它平台工作。这个世界被通过痛苦的道路学得这条经验的可怜的程序员所布满。 关于强制转型的一件有趣的事是很轻易写出看起来对(在其它语言中也许是对的)实际上错的东西。例如,许多应用框架(application framework)要求在派生类中实现虚成员函数时要首先调用它们的基类对应物。假设我们有一个 Window 基类和一个 SpecialWindow 派生类,它们都定义了虚函数 onResize。进一步假设 SpecialWindow 的 onResize 被期望首先调用 Window 的 onResize。这就是实现这个的一种方法,它看起来正确实际并不正确:
class Window { // base class
public:
virtual void onResize() { ... } // base onResize impl
...
};
class SpecialWindow: public Window { // derived class
public:
virtual void onResize() { // derived onResize impl;
static_cast
// then call its onResize;
// this doesn’t work!
... // do SpecialWindow-
} // specific stuff
...
};
我突出了代码中的强制转型。(这是一个新风格的强制转型,但是使用旧风格的强制转型也于事无补。)正像你所期望的,代码将 *this 强制转型为一个 Window。因此调用 onResize 的结果就是调用 Window::onResize。你也许并不期待它没有调用当前对象的那个函数!作为替代,强制转型创建了一个 *this 的基类部分的新的,临时的拷贝,然后调用这个拷贝的 onResize!上面的代码没有调用当前对象的 Window::onResize,然后再对这个对象执行 SpecialWindow 特有的动作——它在对当前对象执行 SpecialWindow 特有的动作之前,调用了当前对象的基类部分的一份拷贝的 Window::onResize。假如 Window::onResize 改变了当前对象(可能性并不小,因为 onResize 是一个 non-const 成员函数),当前对象并不会改变。作为替代,那个对象的一份拷贝被改变。假如 SpecialWindow::onResize 改变了当前对象,无论如何,当前对象将被改变,导致的境况是那些代码使当前对象进入一种病态,没有做基类的变更,却做了派生类的变更。
解决方法就是消除强制转型,用你真正想表达的来代替它。你不应该哄骗编译器将 *this 当作一个基类对象来处理,你应该调用当前对象的 onResize 的基类版本。就是这样:
class SpecialWindow: public Window {
public:
virtual void onResize() {
Window::onResize(); // call Window::onResize
... // on *this
}
...
};
这个例子也表明假如你发现自己要做强制转型,这就是你可能做错了某事的一个信号。在你想用 dynamic_cast 时尤其如此。
在探究 dynamic_cast 的设计意图之前,值得留意的是很多 dynamic_cast 的实现都相当慢。例如,至少有一种通用的实现部分地基于对类名字进行字符串比较。假如你在一个位于四层深的单继续体系中的对象上执行 dynamic_cast,在这样一个实现下的每一个 dynamic_cast 都要付出相当于四次调用 strcmp 来比较类名字的成本。对于一个更深的或使用了多继续的继续体系,付出的代价会更加昂贵。一些实现用这种方法工作是有原因的(它们不得不这样做以支持动态链接)。尽管如此,除了在普遍意义上警惕强制转型外,在性能敏感的代码中,你应该非凡警惕 dynamic_casts。
对 dynamic_cast 的需要通常发生在这种情况下:你要在一个你确信为派生类的对象上执行派生类的操作,但是你只能通过一个基类的指针或引用来操控这个对象。有两个一般的方法可以避免这个问题。

class Window { ... };
public:
void blink();
...
};
typedef // see Item 13 for info
std::vector
VPW winPtrs;
...
for (VPW::iterator iter = winPtrs.begin(); // undesirable code:
iter != winPtrs.end(); // uses dynamic_cast
++iter) {
if (SpecialWindow *psw = dynamic_cast
psw->blink();
}
设法用如下方法代替:
typedef std::vector
VPSW winPtrs;
...
for (VPSW::iterator iter = winPtrs.begin(); // better code: uses
iter != winPtrs.end(); // no dynamic_cast
++iter)
(*iter)->blink();
当然,这个方法不答应你在同一个容器中存储所有可能的 Window 的派生类的指针。为了与不同的窗口类型一起工作,你可能需要多个类型安全(type-safe)的容器。
一个候选方法可以让你通过一个基类的接口操控所有可能的 Window 派生类,就是在基类中提供一个让你做你想做的事情的虚函数。例如,尽管只有 SpecialWindows 能 blink,在基类中声明这个函数,并提供一个什么都不做的缺省实现或许是有意义的:
class Window {
public:
virtual void blink() {} // default impl is no-op;
... // see Item 34 for why
}; // a default impl may be
// a bad idea
class SpecialWindow: public Window {
public:
virtual void blink() { ... }; // in this class, blink
... // does something
};
typedef std::vector
VPW winPtrs; // container holds
// (ptrs to) all possible
... // Window types
for (VPW::iterator iter = winPtrs.begin();
iter != winPtrs.end();
++iter) // note lack of
(*iter)->blink(); // dynamic_cast
无论哪种方法——使用类型安全的容器或在继续体系中上移虚函数——都不是到处适用的,但在很多情况下,它们提供了 dynamic_casting 之外另一个可行的候选方法。当它们可用时,你应该加以利用。
你应该绝对避免的一件东西就是包含了极联 dynamic_casts 的设计,也就是说,看起来类似这样的任何东西:
class Window { ... };
... // derived classes are defined here
typedef std::vector
VPW winPtrs;
...
for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)
{
if (SpecialWindow1 *psw1 = dynamic_cast
else if (SpecialWindow2 *psw2 = dynamic_cast
else if (SpecialWindow3 *psw3 = dynamic_cast
...
}
这样的 C++ 会生成的代码又大又慢,而且很脆弱,因为每次 Window 类继续体系发生变化,所有这样的代码都要必须被检查,以确认是否需要更新。(例如,假如增加了一个新的派生类,在上面的极联中或许就需要加入一个新的条件分支。)看起来类似这样的代码应该总是用基于虚函数的调用的某种东西来替换。 好的 C++ 极少使用强制转型,但在通常情况下完全去除也不实际。例如,从 int 到 double 的强制转型,就是对强制转型的合理运用,虽然它并不是绝对必要。(那些代码应该被重写,声明一个新的类型为 double 的变量,并用 x 的值进行初始化。)就像大多数可疑的结构成分,强制转型应该被尽可能地隔离,典型情况是隐藏在函数内部,用函数的接口保护调用者远离内部的污秽的工作。
Things to Remember
·避免强制转型的随时应用,非凡是在性能敏感的代码中应用 dynamic_casts,假如一个设计需要强制转型,设法开发一个没有强制转型的侯选方案。
·假如必须要强制转型,设法将它隐藏在一个函数中。客户可以用调用那个函数来代替在他们自己的代码中加入强制转型。
·尽量用 C++ 风格的强制转型替换旧风格的强制转型。它们更轻易被注重到,而且他们做的事情也更加明确。

更多精彩
赞助商链接