static_cast, dynamic_cast, reinterpret_cast, const_cast區別比較
(使用vs2010所帶的編譯器) 轉載請注明來源 http://www.cnblogs.com/jerry19880126/
隱式轉換(implicit conversion)
short a=2000;
int b;
b=a;
short是兩字節,int是四字節,由short型轉成int型是寬化轉換(bit位數增多),編譯器沒有warning,如下圖所示。寬化轉換(如char到int,int到long long,int到float,float到double,int到double等)構成隱式轉換,編譯器允許直接轉換。
但若反過來
double a=2000;
short b;
b=a;
此時,是從8字節的double型轉成2字節的short型變量,是窄化轉換,編譯器就會有warning了,如下所示,提醒程序員可能丟失數據。不過需要注意的是,有些隱式轉換,編譯器可能並不給出warning,比如int到short,但數據溢出卻依然會發生。
C風格顯式轉換(C style explicit conversion)
要去掉上述waring很簡單,熟悉C語言的程序員知道,有兩種簡單的寫法(C風格轉換與函數風格轉換):
double a=2000.3;
short b;
b = (short) a; // c-like cast notation
b = short (a); // functional notation
如下圖所示,此時warning就沒了
這種顯式轉換方式簡單直觀,但並不安全,舉一個父類和子類的例子如下:
1 // class type-casting 2 #include <iostream> 3 using namespace std; 4 5 class CDummy { 6 float i,j; 7 CDummy():i(100),j(10){} 8 }; 9 10 class CAddition:public CDummy 11 { 12 int *x,y; 13 public: 14 CAddition (int a, int b) { x=&a; y=b; } 15 int result() { return *x+y;} 16 }; 17 18 int main () { 19 CDummy d; 20 CAddition * padd; 21 padd = (CAddition*) &d; 22 cout << padd->result(); 23 return 0; 24 }
編譯器不報任何錯,但運行結果出錯,如下圖所示:
究其原因,注意這一句:
padd = (CAddition*) &d;
此時父類的指針&d被C風格轉換方式強制轉成了子類的指針了,后面調用了子類的方法result,需要訪問*x,但指針指向的對象本質還是父類的,所以x相當於父類中的i,y相當於父類中的j,*x相當於*i,但i是float型變量(初始化為100),不是地址,所以出錯,如果程序員正是魯莽地對這個地址指向的內存進行寫入操作,那將可能會破壞系統程序,導致操作系統崩潰!
這里有一個重要概念,CAddition*是子類的指針,它的變量padd可以調用子類的方法,但是它指向的是父類的對象,也就是說padd指向的內存空間里存放的是父類的成員變量。深入地說,數據在內存中是沒有“類型”一說的,比如0x3F可能是字符型,也可能是整型的一部分,還可能是地址的一部分。我們定義的變量類型,其實就是定義了數據應該“被看成什么”的方式。
因此padd類指針實質是定義了取值的方式,如padd->x就是一並取出內存空間里的0號單元至3號單元的值(共4個字節),將其拼成32位並當作指針,padd->y則取出內存空間里的4號單元至7號單元(共4個字節),將其拼成32位並當作int型變量。但實際上padd指向的是父類的對象,也就是前4個字節是float型變量,后4個字節也是float型變量。
從這里可以看出,程序員的這種轉換使編譯器“理解”出錯,把牛當成馬了。
從上可見,用C風格的轉換其實是不安全的,編譯器無法看到轉換的不安全。
上行轉換(up-casting)與下行轉換(down-casting)
看到這個,讀者可能會問,哪些轉換不安全?根據前面所舉的例子,可以看到,不安全來源於兩個方面:其一是類型的窄化轉化,會導致數據位數的丟失;其二是在類繼承鏈中,將父類對象的地址(指針)強制轉化成子類的地址(指針),這就是所謂的下行轉換。“下”表示沿着繼承鏈向下走(向子類的方向走)。
類似地,上行轉換的“上”表示沿繼承鏈向上走(向父類的方向走)。
我們給出結論,上行轉換一般是安全的,下行轉換很可能是不安全的。
為什么呢?因為子類中包含父類,所以上行轉換(只能調用父類的方法,引用父類的成員變量)一般是安全的。但父類中卻沒有子類的任何信息,而下行轉換會調用到子類的方法、引用子類的成員變量,這些父類都沒有,所以很容易“指鹿為馬”或者干脆指向不存在的內存空間。
值得一說的是,不安全的轉換不一定會導致程序出錯,比如一些窄化轉換在很多場合都會被頻繁地使用,前提是程序員足夠小心以防止數據溢出;下行轉換關鍵看其“本質”是什么,比如一個父類指針指向子類,再將這個父類指針轉成子類指針,這種下行轉換就不會有問題。
針對類指針的問題,C++特別設計了更加細致的轉換方法,分別有:
static_cast <new_type> (expression)
dynamic_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
const_cast <new_type> (expression)
可以提升轉換的安全性。
static_cast <new_type> (expression) 靜態轉換
靜態轉換是最接近於C風格轉換,很多時候都需要程序員自身去判斷轉換是否安全。比如:
double d=3.14159265;
int i = static_cast<int>(d);
但static_cast已經有安全性的考慮了,比如對於不相關類指針之間的轉換。參見下面的例子:
1 // class type-casting 2 #include <iostream> 3 using namespace std; 4 5 class CDummy { 6 float i,j; 7 }; 8 9 class CAddition { 10 int x,y; 11 public: 12 CAddition (int a, int b) { x=a; y=b; } 13 int result() { return x+y;} 14 }; 15 16 int main () { 17 CDummy d; 18 CAddition * padd; 19 padd = (CAddition*) &d; 20 cout << padd->result(); 21 return 0; 22 }
這個例子與之前舉的例子很像,只是CAddition與CDummy類沒有任何關系了,但main()中C風格的轉換仍是允許的padd = (CAddition*) &d,這樣的轉換沒有安全性可言。
如果在main()中使用static_cast,像這樣:
1 int main () { 2 CDummy d; 3 CAddition * padd; 4 padd = static_cast<CAddition*> (&d); 5 cout << padd->result(); 6 return 0; 7 }
編譯器就能看到這種不相關類指針轉換的不安全,報出如下圖所示的錯誤:
注意這時不是以warning形式給出的,而直接是不可通過編譯的error。從提示信息里可以看到,編譯器說如果需要這種強制轉換,要使用reinterpret_cast(稍候會說)或者C風格的兩種轉換。
總結一下:static_cast最接近於C風格轉換了,但在無關類的類指針之間轉換上,有安全性的提升。
dynamic_cast <new_type> (expression) 動態轉換
動態轉換確保類指針的轉換是合適完整的,它有兩個重要的約束條件,其一是要求new_type為指針或引用,其二是下行轉換時要求基類是多態的(基類中包含至少一個虛函數)。
看一下下面的例子:
1 #include <iostream> 2 using namespace std; 3 class CBase { }; 4 class CDerived: public CBase { }; 5 6 int main() 7 { 8 CBase b; CBase* pb; 9 CDerived d; CDerived* pd; 10 11 pb = dynamic_cast<CBase*>(&d); // ok: derived-to-base 12 pd = dynamic_cast<CDerived*>(&b); // wrong: base-to-derived 13 }
在最后一行代碼有問題,編譯器給的錯誤提示如下圖所示:
把類的定義改成:
class CBase { virtual void dummy() {} };
class CDerived: public CBase {};
再編譯,結果如下圖所示:
編譯都可以順利通過了。這里我們在main函數的最后添加兩句話:
cout << pb << endl;
cout << pd << endl;
輸出pb和pd的指針值,結果如下:
我們看到一個奇怪的現象,將父類經過dynamic_cast轉成子類的指針竟然是空指針!這正是dynamic_cast提升安全性的功能,dynamic_cast可以識別出不安全的下行轉換,但並不拋出異常,而是將轉換的結果設置成null(空指針)。
再舉一個例子:
1 #include <iostream> 2 #include <exception> 3 using namespace std; 4 5 class CBase { virtual void dummy() {} }; 6 class CDerived: public CBase { int a; }; 7 8 int main () { 9 try { 10 CBase * pba = new CDerived; 11 CBase * pbb = new CBase; 12 CDerived * pd; 13 14 pd = dynamic_cast<CDerived*>(pba); 15 if (pd==0) cout << "Null pointer on first type-cast" << endl; 16 17 pd = dynamic_cast<CDerived*>(pbb); 18 if (pd==0) cout << "Null pointer on second type-cast" << endl; 19 20 } catch (exception& e) {cout << "Exception: " << e.what();} 21 return 0; 22 }
輸出結果是:Null pointer on second type-cast
兩個dynamic_cast都是下行轉換,第一個轉換是安全的,因為指向對象的本質是子類,轉換的結果使子類指針指向子類,天經地義;第二個轉換是不安全的,因為指向對象的本質是父類,“指鹿為馬”或指向不存在的空間很可能發生!
最后補充一個特殊情況,當待轉換指針是void*或者轉換目標指針是void*時,dynamic_cast總是認為是安全的,舉個例子:
1 #include <iostream> 2 using namespace std; 3 class A {virtual void f(){}}; 4 class B {virtual void f(){}}; 5 6 int main() { 7 A* pa = new A; 8 B* pb = new B; 9 void* pv = dynamic_cast<void*>(pa); 10 cout << pv << endl; 11 // pv now points to an object of type A 12 13 pv = dynamic_cast<void*>(pb); 14 cout << pv << endl; 15 // pv now points to an object of type B 16 }
運行結果如下:
可見dynamic_cast認為空指針的轉換安全的,但這里類A和類B必須是多態的,包含虛函數,若不是,則會編譯報錯。
reinterpret_cast <new_type> (expression) 重解釋轉換
這個轉換是最“不安全”的,兩個沒有任何關系的類指針之間轉換都可以用這個轉換實現,舉個例子:
class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast<B*>(a);//correct!
更厲害的是,reinterpret_cast可以把整型數轉換成地址(指針),這種轉換在系統底層的操作,有極強的平台依賴性,移植性不好。
它同樣要求new_type是指針或引用,下面的例子是通不過編譯的:
double a=2000.3;
short b;
b = reinterpret_cast<short> (a); //compile error!
const_cast <new_type> (expression) 常量向非常量轉換
這個轉換好理解,可以將常量轉成非常量。
1 // const_cast 2 #include <iostream> 3 using namespace std; 4 5 void print (char * str) 6 { 7 cout << str << endl; 8 } 9 10 int main () { 11 const char * c = "sample text"; 12 char *cc = const_cast<char *> (c) ; 13 Print(cc); 14 return 0; 15 }
從char *cc = const_cast<char *>(c)可以看出了這個轉換的作用了,但切記,這個轉換並不轉換原常量本身,即c還是常量,只是它返回的結果cc是非常量了。
總結
C風格轉換是“萬能的轉換”,但需要程序員把握轉換的安全性,編譯器無能為力;static_cast最接近於C風格轉換,但在無關類指針轉換時,編譯器會報錯,提升了安全性;dynamic_cast要求轉換類型必須是指針或引用,且在下行轉換時要求基類是多態的,如果發現下行轉換不安全,dynamic_cast返回一個null指針,dynamic_cast總是認為void*之間的轉換是安全的;reinterpret_cast可以對無關類指針進行轉換,甚至可以直接將整型值轉成指針,這種轉換是底層的,有較強的平台依賴性,可移植性差;const_cast可以將常量轉成非常量,但不會破壞原常量的const屬性,只是返回一個去掉const的變量。
注:本文中大部分樣例來源自C++標准網站:
http://www.cplusplus.com/doc/tutorial/typecasting/
以及微軟的MSDN:
http://msdn.microsoft.com/en-us/library/cby9kycs
若有理解出錯的地方,望不吝指正。