1. 程式人生 > >static_cast, dynamic_cast, reinterpret_cast, const_cast區別比較

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, 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的變數。