More Effective C++ 條款2 (轉)

worldblog發表於2007-12-09
More Effective C++ 條款2 (轉)[@more@]

 條款2:儘量使用C++風格的型別轉換:namespace prefix = o ns = "urn:schemas--com::office" />

仔細想想地位卑賤的型別轉換功能(cast),其在設計中的地位就象goto語句一樣令人鄙視。但是它還不是無法令人忍受,因為當在某些緊要的關頭,型別轉換還是必需的,這時它是一個必需品。

不過C風格的型別轉換並不代表所有的型別轉換功能。一來它們過於粗魯,能允許你在任何型別之間進行轉換。不過如果要進行更精確的型別轉換,這會是一個優點。在這些型別轉換中存在著巨大的不同,例如把一個指向const的指標(pointer-to-const-)轉換成指向非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風格的型別轉換,你能這樣寫:

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

 // 確實指向了某個物件

  // 否則將丟擲異常

 

dynamic_casts在幫助你瀏覽繼承層次上是有限制的。它不能被用於缺乏虛擬函式的型別上(參見條款24),也不能用它來轉換掉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風格型別轉換方法的強有力的理由,還有第三個理由:也許讓型別轉換符不美觀和鍵入麻煩是一件好事。

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-989995/,如需轉載,請註明出處,否則將追究法律責任。

相關文章