<<static_cast 和 reinterpret_cast>>
作者: 闕榮文(querw@sina.com)
C/C++是強型別語言,不同類型之間的相互轉換是比較麻煩的.但是在編程實踐中,不可避免的要用到類型轉換.有2中類型轉換:隱式類型轉換和強制類型轉換.
1.隱式類型轉換
1.1 提升精度,此種是編譯器自動完成的,安全的.所以編譯的時候不會有任何錯誤或者警告資訊提示.
樣本: <<C++ Primer (第三版)>> P147
int ival = 3;
double dval = 3.14159;
// ival 被提升為 double 類型: 3.0
ival + dval;
1.2 降低精度,也是有編譯器自動完成,會造成精度丟失,所以編譯時間得到一個警告資訊提示.
樣本:
double dval = 3.14159;
// dval的值被截取為 int 值3
int ival = dval;
2.顯式類型轉換
2.1 C風格的強制轉換(包括舊式C++風格的強制轉換)
格式:
類型(運算式); // 舊的C++風格
或者
(類型)運算式 // C風格
樣本: int(dval) 或者 (int)dval
此種強制轉換是比較粗暴直接的,有可能導致精度丟失(如從 double 轉換為 int)或者一些莫名其妙的錯誤(如把 int 轉換為 函數指標),一旦使用了強制轉換,編譯器將不提示任何警告.這也往往成為錯誤的源泉.而且這種錯誤非常難找.我想這也是C++要使用新的強制轉換操作符的原因之一吧.
2.2 C++強制轉換操作符
C++增加了4個關鍵字用於強制類型轉換:
static_cast, reinterpret_cast, const_cast 和 dynamic_cast.
const_cast 用來移除 const,這個沒什麼好說的.
dynamic_cast 需要 RTTI 支援, 主要用於把基類指標轉換為衍生類別指標.這裡的基類指標其實是指向一個衍生類別執行個體,只是類型為基類.
樣本:
// 前提假設: class B 由 class A 派生
A *ptrA = new class B;
B *ptrB = dynamic_cast<B*>(ptrA);
本文主要談談 static_cast 和 reinterpret_cast 的用法和區別.
<<C++程式程式設計語言>>裡有一句話我認為說到點子上了: static_cast 運算子完成*相互關聯類型*之間的轉換. 而 reinterpret_cast 處理*互不相關的類型*之間的轉換.
所謂"相互關聯類型"指的是從邏輯上來說,多多少少還有那麼一點聯絡的類型,比如從 double 到 int,我們知道它們之間還是有聯絡的,只是精度差異而已,使用 static_cast 就是告訴編譯器:我知道會引起精度損失,但是我不在乎. 又如從 void* 到 具體類型指標像 char*,從語義上我們知道 void* 可以是任意類型的指標,當然也有可能是 char* 型的指標,這就是所謂的"多多少少還有那麼一點聯絡"的意思. 又如從衍生類別層次中的上行轉換(即從衍生類別指標到基類指標,因為是安全的,所以可以用隱式類型轉換)或者下行轉換(不安全,應該用
dynamic_cast 代替).
對於static_cast操作符,如果需要截斷,補齊或者指標位移編譯器都會自動完成.注意這一點,是和 reinterpret_cast 的一個根本區別.
"互不相關的類型"指的是兩種完全不同的類型,如從整型到指標類型,或者從一個指標到另一個毫不相干的指標.
樣本:
int ival = 1;
double *dptr = reinterpret_cast<double*>(ival);
或者
int *iptr = NULL;
double *dptr = reinterpret_cast<double*>(iptr);
reinterpret_cast 操作執行的是位元位拷貝,就好像用 memcpy() 一樣.
int *iptr = reinterpret_cast<int*>(1);
double *dptr = reinterpret_cast<double*>(2);
memcpy(&dptr, &iptr, sizeof(double*)); // 等效於 dptr = reinterpret_cast<double*>(iptr); 結果 dptr 的值為1;
上面這個樣本也說明了 reinterpret_cast 的意思:編譯器不會做任何檢查,截斷,補齊的操作,只是把位元位拷貝過去.
所以 reinterpret_cast 常常被用作不同類型指標間的相互轉換,因為所有類型的指標的長度都是一致的(32位系統上都是4位元組),按位元位拷貝後不會損失資料.
3. 編程實踐中幾種典型的應用情境
3.1 數值精度提示或者降低,包括把無符號型轉換為帶符號型(也是精度損失的一種),用 static_cast 可以消除編譯器的警告資訊,前面提到好幾次了.
3.2 任意類型指標到 void*, 隱式類型轉換,自動完成. 看看 memcpy 的原型
void *memcpy(
void *dest,
const void *src,
size_t count
);
參數定義為 void* 是有道理的,不管我們傳入什麼類型的指標都符合語義,並且不會有編譯器警告.
3.3 void* 到任意類型指標, 用 static_cast 和 reinterpret_cast 都可以,這是由 void* 是通用指標這個語義決定的.我個人傾向用 reinterpret_cast,表達要"重新解釋"指標的語義.
3.4 不同類型指標間的相互轉換用 reinterpret_cast.
3.5 int 型和指標類型間的相互轉換用 reinterpret_cast.
比如我寫代碼的時候經常這樣做: new 一個 struct,然後把指標返回給外部函數作為一個"控制代碼",我不希望外部函數知道這是一個指標,只需要外部函數在調用相關函數時把這個"控制代碼"重新傳回來.這時,就可以把指標轉換為一個 int 型返回. 這是 reinterpret_cast 存在的絕佳理由.
struct car
{
int doors;
int height;
int length;
float weight;
};
int create_car()
{
car *c = new car;
return reinterpret_cast<int>(c);
}
int get_car_doors(int car_id)
{
car *c = reinterpret_cast<car*>(car_id);
return c->doors;
}
void destroy_car(int car_id)
{
car *c = reinterpret_cast<car*>(car_id);
delete c;
}
如上,外部函數不需要知道 struct car 的具體定義,只需要調用 create_car() 得到一個 car id,然後用此 car_id 調用其他相關函數即可,至於 car_id 是什麼,根本沒必要關心.
3.6 衍生類別指標和基類指標間的相互轉換.
3.6.1 衍生類別指標到基類指標用隱式類型轉換(直接賦值)或者用 static_cast. 顯然不應該也沒必要用 reinterpret_cast.
3.6.2 基類指標到衍生類別指標用 dynamic_cast (運行期檢查)或者 static_cast (運行期不檢查,由程式員保證正確性). 考慮到C++物件模型的記憶體分布可能引起的指標位移問題,絕對不能用 reinterpret_cast.
後記
幾乎所有提到 reinterpret_cast 的書籍都要附帶說什麼"不可移植","危險"之類的詞,好像 reinterpret_cast 是洪水猛獸,碰不得摸不得.其實理解了之後就知道沒什麼神秘的,存在即是理由,該用的時候就要大膽的用,否則C++保留這個關鍵字幹什麼? 關鍵是程式員應該清楚的知道自己要的結果是什麼,如此,就是用C風格的強制轉換又有何妨?