static_cast, dynamic_cast, reinterpret_cast, const_cast區別比較

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的變量。

發佈了7 篇原創文章 · 獲贊 13 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章