?????????C++ 引入了 const_cast, reinterpret_cast 之類的新的顯式類型轉(zhuǎn)換方式,不僅大多數(shù) C 程序員覺得不是很習(xí)慣,就連某些有經(jīng)驗(yàn)的C++ 程序員都會在一些細(xì)節(jié)上犯錯(cuò)。誠然,既然我們可以簡單的寫出:

int i = (int)p;// p is a pointer

這樣的顯式轉(zhuǎn)換,為什么還要使用

int i = reinterpret_cast( p );

這么復(fù)雜的形式呢?這篇文章的目的是簡單介紹 C++ 的類型轉(zhuǎn)換系統(tǒng),并對使用和擴(kuò)展進(jìn)行一些討論


1. 為什么需要類型轉(zhuǎn)換?
類型轉(zhuǎn)換被用來把一個(gè)類型的值轉(zhuǎn)換成另一個(gè)類型。類似于 C++ 這樣的編程語言是強(qiáng)類型的,因此每一個(gè)值都有它相應(yīng)的類型。當(dāng)你需要把一個(gè)值轉(zhuǎn)換為另一個(gè)類型時(shí),你需要使用下列方式中的一種:隱式轉(zhuǎn)換,顯式轉(zhuǎn)換和無法轉(zhuǎn)換。假設(shè)我們使用老式的顯式轉(zhuǎn)換:

char c = 'a';
int* p = NULL;
int a = c;// 隱式轉(zhuǎn)換
a=(int) p; // 顯式轉(zhuǎn)換
double d=(double) p;// 無法轉(zhuǎn)換

通常,隱式轉(zhuǎn)換意味著編譯器認(rèn)為你的轉(zhuǎn)換是合理的或者是安全的;顯式轉(zhuǎn)換意味著編譯器能夠找到一個(gè)轉(zhuǎn)換方式,但是它不保證這個(gè)轉(zhuǎn)換是否安全,所以需要程序員額外指出;而無法轉(zhuǎn)換則意味著編譯器無法發(fā)現(xiàn)一條直接的路徑來進(jìn)行類型轉(zhuǎn)換。

2. 為什么需要 C++ 風(fēng)格的顯式轉(zhuǎn)換?
C++ 風(fēng)格的顯式轉(zhuǎn)換為我們提供了更精確的語義和對其進(jìn)一步擴(kuò)展的可能。在 C 語言中,我們可以用一個(gè)簡單的 (int*) 來完成下面的轉(zhuǎn)換:

const char* s = 0;
int* p = (int*) s;

這一句語句,不僅轉(zhuǎn)換了類型,還把 const 也去掉了。通常如果我們看到一句游離的顯式轉(zhuǎn)換,我們不能立即知道作者的意圖,這也為今后的錯(cuò)誤埋下了伏筆。C++ 風(fēng)格的現(xiàn)實(shí)轉(zhuǎn)換通過區(qū)分各種轉(zhuǎn)換情況來增加安全性:通過 const_cast 來取消 const、volatile 之類的修飾,通過 static_cast 來做相關(guān)類型的轉(zhuǎn)換,通過 reinterpret_cast 來做低級的轉(zhuǎn)換,...。有一個(gè)例子可以說明這些轉(zhuǎn)換的“精確”程度:

class Interface
{
int member;
};

class Base {};

class Derived : public Interface, public Base {};

int main()
{
Base* b = (Base*)100;
Derived* d1 = reinterpret_cast( b );
Derived* d2 = static_cast( b );
}

這段代碼中,兩個(gè) cast 都是合法的,但是意義不同。前者意味著“把 b 的指針的值直接賦給 d1,并且把它作為 Derived 類型解釋”,后者意味著“根據(jù)相關(guān)類型信息來做轉(zhuǎn)換,如果可能,對 b 指針做一些偏移”。在上面這個(gè)例子里面,d1 和 d2 是不相等的!可能由于很多書上都說:如果你要在指針之間互相轉(zhuǎn)換,應(yīng)該使用 reinterpret_cast,所以不少程序員使用 reinterpret_cast 來做一切指針類型轉(zhuǎn)換,雖然通常他們不會得到錯(cuò)誤,但是的確是不小的隱患。

本來我這個(gè)例子的黑體部分使用的是0,有一位網(wǎng)友指出,C++在進(jìn)行cast的時(shí)候會對0進(jìn)行特殊處理。也就是說,在上面的例子中,如果使用0,那么d1和d2是一樣的。

3. 一些例子

(1) itf_cast
在 COM 中,如果我有一個(gè)指向 IHtmlDocument 的接口指針,并且我想從中獲得一個(gè) IPersistFile 的指針,我們可以用下述代碼:

IPersistFile* pPersistFile;
if( FAILED( pHtmlDocument->QueryInterface(IID_IPersistFile, (LPVOID*) &pPersistFile) ) )
throw something;

這段代碼很簡單但是不夠直接,所以我們可以實(shí)現(xiàn)這樣一個(gè)函數(shù):

template
T1 itf_cast(T2 v2)
{
if( v2 == 0 )
return 0;
T1 v1;
if( FAILED( v2->QueryInterface( __uuidof(*v1), (LPVOID*)&v1 ) ) )
throw bad_cast();
return v1;
}

然后我們可以把上面的語句寫成

pPersistFile = itf_cast( pHtmlDocument );

這非常的直觀。仔細(xì)的讀者可能會發(fā)現(xiàn) __uuidof 不是標(biāo)準(zhǔn)的 C++ 所定義的,而是 VC 的一個(gè)擴(kuò)展。事實(shí)上,在這里你可以用 traits 很簡單的實(shí)現(xiàn)同樣的功能。

(2) stream_cast
有時(shí)候,我們經(jīng)常會遇到一些自定義類型之間轉(zhuǎn)換問題,譬如說 std::string 和 double,甚至 std::string 和 RECT 之類的轉(zhuǎn)換。如果參與轉(zhuǎn)換的兩個(gè)類型都定義了輸入輸出運(yùn)算(精確的說,源類型支持輸出運(yùn)算,目的類型支持輸入運(yùn)算),那么我們可以用以下的方式來進(jìn)行轉(zhuǎn)換:

template
T1 stream_cast(const T2& v2)
{
std::stringstream str;
str << v2;
T1 t1;
str >> t1;
if( !str )
throw bad_cast();
return t1;
}

這樣一來,你可以用以下語句進(jìn)行轉(zhuǎn)換:

string s("0.5");
double d = stream_cast( s );

(3) safe_cast
有時(shí)候我們希望我們的顯式轉(zhuǎn)換是安全的,這里安全的定義是,這個(gè)值在兩個(gè)類型中的表示都是無損的。也就是說,(T2)(T1)v1 == v1。那我們可以定義這樣的顯式轉(zhuǎn)換:

template
T1 stream_cast(const T2& v2)
{
if( (T2)(T1) v2 != v2 )
throw bad_cast();
return (T1) v2;
}

于是,stream_cast(1000); 這樣的語句就會拋出異常。