static_cast, dynamic_cast, reinterpret_cast, const_cast區別比較
原帖 :http://www.cnblogs.com/jerry19880126/archive/2012/08/14/2638192.html
隱式轉換(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, intb) { 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的變數。