這四個操作符:
static_cast, const_cast, dynamic_cast, 和reinterpret_cast
static_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 有這樣的功能。
const_cast
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<SpecialWidget*>(&csw));
// 正確,csw 的const 被顯示地轉換掉(
// csw 和sw 兩個變數值在update
//函數中能被更新)
update((SpecialWidget*)&csw);
// 同上,但用了一個更難識別
//的C 風格的類型轉換
Widget *pw = new SpecialWidget;
update(pw); // 錯誤!pw 的類型是Widget*,但是
// update 函數處理的是SpecialWidget*類型
update(const_cast<SpecialWidget*>(pw));
// 錯誤!const_cast 僅能被用在影響
// constness or volatileness 的地方上。,
// 不能用在向繼承子類進行類型轉換。
到目前為止,const_cast 最普通的用途就是轉換掉對象的const 屬性。
dynamic_cast
它被用於安全地沿著類的繼承關係向下進
行類型轉換。這就是說,你能用dynamic_cast 把指向基類的指標或引用轉換成指向其派生
類或其兄弟類的指標或引用,而且你能知道轉換是否成功。失敗的轉換將返回null 指標(當對
指標進行類型轉換時)或者拋出異常(當對引用進行類型轉換時):
Widget *pw;
...
update(dynamic_cast<SpecialWidget*>(pw));
// 正確,傳遞給update 函數一個指標
// 是指向變數類型為SpecialWidget 的pw 的指標
// 如果pw 確實指向一個對象,
// 否則傳遞過去的將使null 指標。
void updateViaRef(SpecialWidget& rsw);
updateViaRef(dynamic_cast<SpecialWidget&>(*pw));
//正確。 傳遞給updateViaRef 函數
// SpecialWidget pw 指標,如果pw
// 確實指向了某個對象
// 否則將拋出異常
dynamic_casts 在協助你瀏覽繼承層次上是有限制的。它不能被用於缺乏虛函數的類型
上(參見條款M24),也不能用它來轉換掉constness:
int firstNumber, secondNumber;
...
double result = dynamic_cast<double>(firstNumber)/secondNumber;
// 錯誤!沒有繼承關係
const SpecialWidget sw;
...
update(dynamic_cast<SpecialWidget*>(&sw));
// 錯誤! dynamic_cast 不能轉換
// 掉const。
如你想在沒有繼承關係的類型中進行轉換,你可能想到static_cast。如果是為了去除
const,你總得用const_cast。
reinterpret_cast
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<FuncPtr>(&doSomething);
轉換函式指標的代碼是不可移植的(C++不保證所有的函數指標都被用一樣的方法表
示),在一些情況下這樣的轉換會產生不正確的結果(參見條款M31),所以你應該避免轉換
函數指標類型,除非你處於著背水一戰和尖刀架喉的危急時刻。一把鋒利的刀。一把非常鋒
利的刀。
總結
如果你使用的編譯器缺乏對新的類型轉換方式的支援,你可以用傳統的類型轉換方法代
替static_cast, const_cast, 以及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 風格類型轉換方法的強有力的理由。還有第
三個理由:也許讓類型轉換符不美觀和鍵入麻煩是一件好事。