C++ 中的 cast(顯式類型轉換)

C++ 引入了 const_cast, reinterpret_cast 之類的新的顯式類型轉換方式,不僅大多數 C 程序員覺得不是很習慣,就連某些有經驗的C++ 程序員都會在一些細節上犯錯。誠然,既然我們可以簡單的寫出:

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

這樣的顯式轉換,爲什麼還要使用

int i = reinterpret_cast( p );

這麼複雜的形式呢?這篇文章的目的是簡單介紹 C++ 的類型轉換系統,並對使用和擴展進行一些討論。
1. 爲什麼需要類型轉換?
類型轉換被用來把一個類型的值轉換成另一個類型。類似於 C++ 這樣的編程語言是強類型的,因此每一個值都有它相應的類型。當你需要把一個值轉換爲另一個類型時,你需要使用下列方式中的一種:隱式轉換,顯式轉換和無法轉換。假設我們使用老式的顯式轉換:

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

通常,隱式轉換意味着編譯器認爲你的轉換是合理的或者是安全的;顯式轉換意味着編譯器能夠找到一個轉換方式,但是它不保證這個轉換是否安全,所以需要程序員額外指出;而無法轉換則意味着編譯器無法發現一條直接的路徑來進行類型轉換。

2. 爲什麼需要 C++ 風格的顯式轉換?
C++ 風格的顯式轉換爲我們提供了更精確的語義和對其進一步擴展的可能。在 C 語言中,我們可以用一個簡單的 (int*) 來完成下面的轉換:

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

這一句語句,不僅轉換了類型,還把 const 也去掉了。通常如果我們看到一句遊離的顯式轉換,我們不能立即知道作者的意圖,這也爲今後的錯誤埋下了伏筆。C++ 風格的現實轉換通過區分各種轉換情況來增加安全性:通過 const_cast 來取消 const、volatile 之類的修飾,通過 static_cast 來做相關類型的轉換,通過 reinterpret_cast 來做低級的轉換,...。有一個例子可以說明這些轉換的“精確”程度:

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 );
}

這段代碼中,兩個 cast 都是合法的,但是意義不同。前者意味着“把 b 的指針的值直接賦給 d1,並且把它作爲 Derived 類型解釋”,後者意味着“根據相關類型信息來做轉換,如果可能,對 b 指針做一些偏移”。在上面這個例子裏面,d1 和 d2 是不相等的!可能由於很多書上都說:如果你要在指針之間互相轉換,應該使用 reinterpret_cast,所以不少程序員使用 reinterpret_cast 來做一切指針類型轉換,雖然通常他們不會得到錯誤,但是的確是不小的隱患。

本來我這個例子的黑體部分使用的是0,有一位網友指出,C++在進行cast的時候會對0進行特殊處理。也就是說,在上面的例子中,如果使用0,那麼d1和d2是一樣的。

3. 一些例子

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

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

這段代碼很簡單但是不夠直接,所以我們可以實現這樣一個函數:

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 );

這非常的直觀。仔細的讀者可能會發現 __uuidof 不是標準的 C++ 所定義的,而是 VC 的一個擴展。事實上,在這裏你可以用 traits 很簡單的實現同樣的功能。

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

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

這樣一來,你可以用以下語句進行轉換:

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

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

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章