c++之運行時類型識別


 這一部分將討論C++中兩個支持”現代 面向對象程序設計“的特征。運行時類型識別(Run-Time Type Identification,RTTI)和強制轉換運算符。C++的初始定義並沒有包含這兩個特征,增加他們是為了增強C++對運行時多態的支持。RTTI允許應用程序在執行期間標識一個對象的類型。而強制轉換運算符可以給你帶來更安全的、更加可控的類型轉換方法。你將會看到強制轉換運算符之一dynamic_cast是與RTTI直接相關的。所以將”強制轉換運算符“和RTTI放在一起討論。

      對你來說,運行時的類型識別可能是個新東西,因為在非多態語言(例如:C)中找不到這個概念的。非多態語言不需要運行時的類型信息,因為每個對象的類型在編譯時就已經確定了(例如:在寫程序的時候我們指定了對象的類型)。但是在支持多態的語言中(例如C++),可能存在這種情況:在編譯時你並不知道某個對象的類型信息,而只有在程序運行時才能獲得對象的准確信息。你已經知道,C++是通過類的層次結構、虛函數以及基類指針來實現多態的。基類指針可以用來指向基類的對象或者其派生類的對象,也就是說,我們並不總是能夠在任何時刻都預先知道基類指針所指向對象的實際類型。因此,必須在程序中使用“運行時類型識別”來識別對象的實際類型。

      我們可以通過typeid來獲得一個對象的類型,在使用typeid之前,你必須在程序中包含頭文件<typeinfo.h>。typeid的最常見使用形式如下:typeid(object).其中,object是你想獲得“類型信息”的對象。這個對象可以是任意的類型,包括C++的內置類型以及你創建的類型,typeid將返回一個type_info 類型的對象引用 來描述object的類型信息。在type_info中定義了下面這些公有成員:

bool operator==(const type_info &ob);

bool operator!=(const type_info &ob);

bool before(const type_info &ob);

const char* name();

其中重載的運算符==和!=可以用來比較類型信息,如果在類型信息的列表中"調用before的對象類型"在"對象ob的類型"之前,那么函數before將返回 真,否則返回假(這個函數通常在內部使用,函數的返回值與“繼承”或者“類的層次結構”是沒有關系的)。函數name()將返回一個指向類型名字的指針:下面是使用typeid的示例程序:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class myClass  
  4. {};  
  5. void main()    
  6. {    
  7.     cout<<"Start"<<endl;   
  8.     int i,j;  
  9.     float f;  
  10.     myClass ob;  
  11.     cout<<"The type of i is:"<<typeid(i).name()<<endl;  
  12.     cout<<"The type of f is:"<<typeid(f).name()<<endl;  
  13.     cout<<"The type of i ob:"<<typeid(ob).name()<<endl;  
  14.     if(typeid(i)==typeid(j))  
  15.         cout<<"The type i and j are the same"<<endl;  
  16.     if(typeid(i)!=typeid(f))  
  17.         cout<<"The type i and f are the same"<<endl;  
  18.     cout<<"End"<<endl;    
  19. }    
  20. //輸出結果  
  21. Start  
  22. The type of i is:int  
  23. The type of f is:float  
  24. The type of i ob:class myClass  
  25. The type i and j are the same  
  26. The type i and f are the same  
  27. End  
 

我們可以將typeid應用於多態基類(多態類是包含虛函數的類)指針。這也可能是typeid最重要的用途。在這種情況中,typeid將自動的返回指針所指向對象的實際類型,返回值可能是基類對象或者從基類中派生類的對象().因此,通過使用typeid你可以在運行時確定基類指針指向對象的實際類型。下面的程序說明了這種用法:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class base  
  4. {  
  5.     virtual void f(){}//基類是多態類  
  6. };  
  7. class Derived1 : public base  
  8. {};  
  9. class Derived2 : public base  
  10. {};  
  11. void main()    
  12. {    
  13.     cout<<"Start"<<endl;   
  14.       
  15.     base *p,baseob;  
  16.     Derived1 ob1;  
  17.     Derived2 ob2;  
  18.     p=&baseob;  
  19.     cout<<"p is pointing to object of type is:"<<typeid(*p).name()<<endl;  
  20.     p=&ob1;  
  21.     cout<<"p is pointing to object of type is:"<<typeid(*p).name()<<endl;  
  22.     p=&ob2;  
  23.     cout<<"p is pointing to object of type is:"<<typeid(*p).name()<<endl;  
  24.     cout<<"End"<<endl;    
  25. }    
  26. //輸出結果  
  27. p is pointing to object of type is:base  
  28. p is pointing to object of type is:Derived1  
  29. p is pointing to object of type is:Derived2  
 

在程序中,當我們對“多態類型的基類指針”使用typeid時,就可以在運行時確定指針指向對象的實際類型,並輸出這個類型的名字。如果typeid被應用於非多態指針類,那么我們得到的將是指針被聲明的類型。也就是說,此時typeid並不會返回指針所指向對象的實際類型。我們可以驗證這種情況,將虛函數f()從類base中注釋掉並觀察修改后程序的輸出結果,你將看到,程序輸出的每個對象的類型將變成base,因為指針p的類型是base。

      多態類對象“引用”的用法類似於對象指針,當typeid應用於“多態類對象的引用"時,他將返回引用指向對象的實際類型,可能是基類,也可能是派生類型。當對象引用被作為參數傳遞給函數時,我們需要經常使用typeid來獲得對象的實際類型。例如:下面程序中的函數WhatType()聲明了一個base類型的引用參數。這意味着可以將”base類或者base類的派生類“的對象引用作為參數傳遞給函數。當對這個參數使用typeid是,我們就可以得到傳遞給函數的對象的實際類型。

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class base  
  4. {  
  5.     virtual void f(){}//基類是多態類  
  6. };  
  7. class Derived1 : public base  
  8. {};  
  9. class Derived2 : public base  
  10. {};  
  11. void WhatType(base &ob)  
  12. {  
  13.     cout<<"ob is referercing an object of type:"<<typeid(ob).name()<<endl;  
  14. }  
  15. void main()    
  16. {    
  17.     cout<<"Start"<<endl;   
  18.       
  19.     int i;  
  20.     base baseob;  
  21.     Derived1 ob1;  
  22.     Derived2 ob2;  
  23.     WhatType(baseob);  
  24.     WhatType(ob1);  
  25.     WhatType(ob2);  
  26.       
  27.     cout<<"End"<<endl;    
  28. }    
  29. //輸出結果  
  30. ob is pointing to object of type is:base  
  31. ob is pointing to object of type is:Derived1  
  32. ob is pointing to object of type is:Derived2  
 

typeid還有第二種使用形式,將一個類型名字作為參數,如下所示:

typeid(type_name);

例如:下面的語句時完全正確的:cout<<typeid(int).name();

使用這種形式的typeid的主要目的在於,獲得一個描述特定類型的type_info對象,這個對象可以被用在類型比較語句中。

強制轉換運算符

      C++共定義了5種“強制轉換運算符”。第一種是傳統的強制轉換,它是C++初始定義的一部分,其余4種強制轉換運算符后來才被C++定義中,它們分別是dynamic_cast、const_cast、reinterpret_cast、static_cast。我們可以使用這些運算符來增強對強制轉換的控制。下面分別學習每種強制轉換運算符。

1.1dynamic_cast---對 多態類型 進行 運行時 的 強制轉換

      dynamic_cast可能是所有新增“強制轉換運算符”中最重要的一個。dynamic_cast執行的是運行時強制轉換。這樣就可以確保強制轉換的合法性。如果在執行dynamic_cast時的強制轉換是非法的,那么強制轉換將會失敗。dynamic_cast的通用形式如下:

dynamic_cast<target-type> (expr);其中,target-type指定了強制轉換的目標類型。expr是需要進行強制轉換的表達式。目標類型必須是“指針類型”或者“引用類型”。而需要進行強制轉換的表達式必須能夠被求值為“指針類型”或者“引用類型”的結果。也就是說,我們可以使用dynamic_cast將指針或者引用從一種類型轉換為另一種類型。

      dynamic_cast主要被用來執行 多態類型之間的強制轉換。例如假設2個多態類B和D,其中D是派生於B的。這樣我們就可以使用dynamic_cast將指針從類型D*轉換為類型B*。理由是基類型的指針通常可以指向派生類的對象。然而,只有當指針指向的對象“確實是”類D的對象時,dynamic_cast才能將指針從類型D*轉換為類型B*。通常,只有“需要被強制轉換的指針(或引用)”指向的對象是目標類型 或者目標類型的派生類型時,強制轉換運算符才會成功;否則,強制轉換運算符失敗。如果在轉換指針的類型時失敗,那么經過dynamic_cast運算后得到的指針將會使空值。如果在轉換引用的類型時失敗,那么dynamic_cast將會拋出bad_cast類型的異常。

      下面是個簡單的示例程序,其中假設Base是多態類,並且Derived派生與Base。

 

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class Base  
  4. {  
  5.     virtual void f(){}//基類是多態類  
  6. };  
  7. class Derived : public Base  
  8. {};  
  9. void main()    
  10. {    
  11.     cout<<"Start"<<endl;   
  12.       
  13.     Base *bp,b_ob;  
  14.     Derived *dp,d_ob;  
  15.     bp=&d_ob;//基類指針指向派生類型的對象  
  16.     dp=dynamic_cast<Derived* >(bp);//將指向派生類的基類指針 強制轉換為派生類型的指針,真確。  
  17.     if(dp) cout<<"Cast OK"<<endl;  
  18.       
  19.     cout<<"End"<<endl;    
  20. }    
  21. //運行結果:  
  22. Start  
  23. Cast OK  
  24. End  
 

上面的代碼中,將基類型的指針bp強制轉換為派生類型的指針dp是可行的,因為指針bp指向的對象確實是一個Dereived類型的對象。因此,程序將會輸出Cast Ok。但在在下面的示例中,強制轉換時失敗的,因為bp指向的對象是一個Base類型的對象,此時,將基類型的指針強制轉換為派生類型的指針是非法的。除非指針指向的對象時派生類型的對象。

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class Base  
  4. {  
  5.     virtual void f(){}//基類是多態類  
  6. };  
  7. class Derived : public Base  
  8. {};  
  9. void main()    
  10. {    
  11.     cout<<"Start"<<endl;   
  12.       
  13.     Base *bp,b_ob;  
  14.     Derived *dp,d_ob;  
  15.     bp=&b_ob;//基類指針指向派生類型的對象  
  16.     dp=dynamic_cast<Derived* >(bp);//將指向派生類的基類指針 強制轉換為派生類型的指針,真確。  
  17.     if(dp)   
  18.         cout<<"Cast OK."<<endl;  
  19.     else  
  20.         cout<<"Cast Fails."<<endl;  
  21.       
  22.     cout<<"End"<<endl;    
  23. }    
  24. //運行結果:  
  25. Start  
  26. Cast Fails.  
  27. End  
 

下面的程序說明了dynamic_cast的多種情況:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class Base//基類是多態類  
  4. {  
  5. public:  
  6.     virtual void f()  
  7.     {  
  8.         cout<<"Inside Base."<<endl;  
  9.     }  
  10. };  
  11. class Derived : public Base  
  12. {  
  13. public:  
  14.     void f()  
  15.     {  
  16.         cout<<"Inside Derived."<<endl;  
  17.     }  
  18. };  
  19. void main()    
  20. {    
  21.     cout<<"Start"<<endl;   
  22.       
  23.     Base *bp,b_ob;  
  24.     Derived *dp,d_ob;  
  25.     //-------------------------------------  
  26.     dp=dynamic_cast<Derived*>(&d_ob);//派生類對象的指針“強制轉換為”派生類對象的指針 OK  
  27.     if(dp)  
  28.     {  
  29.         cout<<"Cast from Derived* to Derived* Ok."<<endl;  
  30.         dp->f();  
  31.     }  
  32.     else  
  33.         cout<<"Error!"<<endl;  
  34.     //--------------------------------------  
  35.     bp=dynamic_cast<Base*>(&d_ob);//派生類對象的指針“強制轉換為”基類對象的指針 OK  
  36.     if(bp)  
  37.     {  
  38.         cout<<"Cast from Derived* to Base* OK."<<endl;  
  39.         bp->f();  
  40.     }  
  41.     else  
  42.         cout<<"Error!"<<endl;  
  43.     //---------------------------------  
  44.     bp=dynamic_cast<Base*>(&b_ob);//基類對象的指針“強制轉換為”基類對象的指針 OK  
  45.     if(bp)  
  46.     {  
  47.         cout<<"Cast from Base* to Base* OK."<<endl;  
  48.         bp->f();  
  49.     }  
  50.     else  
  51.         cout<<"Error!"<<endl;  
  52.     //-------------------------------------  
  53.     bp=dynamic_cast<Derived*>(&b_ob);//基類對象的指針“強制轉換為”派生類對象的指針 Error  
  54.     if(bp)  
  55.     {  
  56.         cout<<"Cast from Base* to Base* OK."<<endl;  
  57.         bp->f();  
  58.     }  
  59.     else  
  60.         cout<<"Cast from Base* to Base* not OK."<<endl;  
  61.     //=====================================================  
  62.     bp=&d_ob;  
  63.     dp=dynamic_cast<Derived*>(bp);//指向派生類的基類指針“強制轉換為”派生類對象的指針 OK  
  64.     if(dp)  
  65.     {  
  66.         cout<<"casting bp to a Drived* OK."<<endl;  
  67.         cout<<"because bp is really pointing to a Derived object."<<endl;  
  68.         dp->f();  
  69.     }  
  70.     else   
  71.         cout<<"Error!"<<endl;  
  72.     //-------------------------------------------------------  
  73.     bp=&b_ob;//bp指向基類型的對象  
  74.     dp=dynamic_cast<Derived*>(bp);//指向基類型對象的指針“強制轉換為”派生類對象的指針 OK  
  75.     if(dp)  
  76.         cout<<"Error!"<<endl;  
  77.     else  
  78.     {  
  79.         cout<<"Now casting bp to a Drived* is not ok."<<endl;  
  80.         cout<<"because bp is really pointing to a Base object."<<endl;  
  81.     }  
  82.     //---------------------------------------------------------  
  83.     dp=&d_ob;//dp指向基類型的對象  
  84.     bp=dynamic_cast<Base*>(dp);//指向派生類型對象的指針“強制轉換為”指向基類對象的指針 OK  
  85.     if(dp)  
  86.     {  
  87.         cout<<"Casting dp to a base* is ok"<<endl;  
  88.         bp->f();  
  89.     }  
  90.     else  
  91.         cout<<"Error"<<endl;  
  92.     cout<<"End"<<endl;    
  93. }    
  94. //運行結果:  
  95. Start  
  96. Cast from Derived* to Derived* Ok.  
  97. Inside Derived.  
  98. Cast from Derived* to Base* OK.  
  99. Inside Derived.  
  100. Cast from Base* to Base* OK.  
  101. Inside Base.  
  102. Cast from Base* to Base* not OK.  
  103. casting bp to a Drived* OK.  
  104. because bp is really pointing to a Derived object.  
  105. Inside Derived.  
  106. Now casting bp to a Drived* is not ok.  
  107. because bp is really pointing to a Base object.  
  108. Casting dp to a base* is ok  
  109. Inside Derived.  
  110. End  
 

在某些特定的情況下,運算符dynamic_cast可以用來代替typeid。例如:假設base是derived的多態基類,那么在下面的代碼中,只有當指針bp指向的對象確實是derived類型的對象時,才把指針bp指向對象的地址賦給指針dp:

base* bp;

derived *dp;

//......

if( typeid(*bp) == typeid(*dp) ) dp=(derived*)bp;

上面的代碼使用了傳統形式的強制轉換來轉換指針的類型。這個轉換是安全的,因為if語句在類型轉換之前使用了typeid來檢查轉換的合法性。但是我們可以用更好的方法來代替上面代碼實現的功能,那就是用運算符dynamic_cast來代替typeid和if語句:

dp=dynamic_cast<derived*>(bp);

因為只有“需要轉換類型的指針”所指向的“對象”是“目標類型的對象”或者是“從目標類型派生的類型的對象”,dynamic_cast才會成功。所以在執行強制轉換語句之后,指針dp將或者是一個空值或者是一個指向derived類型對象的指針。由於dynamic_cast只有在轉換合法時才能成功。因此它能夠簡化在特定情況中的邏輯判斷。下面的程序演示了如何使用dynamic_cast來代替typeid.程序執行2組相同的操作:首先使用typeid,然后使用dynamic_cast.

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. #include <typeinfo.h>  
  3. class Base//基類是多態類  
  4. {  
  5. public:  
  6.     virtual void f()  
  7.     {  
  8.     }  
  9. };  
  10. class Derived : public Base  
  11. {  
  12. public:  
  13.     void derivedOnly()  
  14.     {  
  15.         cout<<"Is a derived object."<<endl;  
  16.     }  
  17. };  
  18. void main()    
  19. {    
  20.     cout<<"Start"<<endl;   
  21.       
  22.     Base *bp,b_ob;  
  23.     Derived *dp,d_ob;  
  24.     //******************************  
  25.     //使用typeid  
  26.     //******************************  
  27.     bp=&b_ob;  
  28.     iftypeid(*bp) == typeid(Derived) )  
  29.     {  
  30.         dp=(Derived*)bp;  
  31.         dp->derivedOnly();  
  32.     }  
  33.     else  
  34.         cout<<"Cast from base to derived failed."<<endl;  
  35.     //--------------------------------------------------  
  36.     bp=&d_ob;  
  37.     iftypeid(*bp) == typeid(Derived) )  
  38.     {  
  39.         dp=(Derived*)bp;  
  40.         dp->derivedOnly();  
  41.     }  
  42.     else  
  43.         cout<<"Error,cast should work!"<<endl;  
  44.     //******************************  
  45.     //使用dynamic_cast  
  46.     //******************************  
  47.     bp=&b_ob;  
  48.     dp=dynamic_cast<Derived*>(bp);  
  49.     if(dp)  
  50.         dp->derivedOnly();  
  51.     else  
  52.         cout<<"Cast from base to derived failed."<<endl;  
  53.     //---------------------------------------------------  
  54.     bp=&d_ob;  
  55.     dp=dynamic_cast<Derived*>(bp);  
  56.     if(dp)  
  57.         dp->derivedOnly();  
  58.     else  
  59.         cout<<"Error,cast should work!"<<endl;  
  60.     cout<<"End"<<endl;    
  61. }    
  62. //運行結果:  
  63. Start  
  64. Cast from base to derived failed.  
  65. Is a derived object.  
  66. Cast from base to derived failed.  
  67. Is a derived object.  
  68. End  
 

從程序中可以看到,dynamic_cast簡化了從基類指針轉化為派生類指針時所需要的邏輯判斷。最后一點:強制轉換運算符dynamic_cast也可以用在模板類中。

const_cast

const_cast 去掉對象的const和/或volatile屬性。在強制轉換中,const_cast被用來顯式的去掉變量的const或者volatile屬性。在這種強制轉換運算中,目標類型與源類型必須是一致的。只是源類型中的const或者volatile屬性在目標類型中被去掉了。我們經常使用const_cast 去掉const屬性。const_cast的通用形式如下:

const_cast<type>(expr);其中type指定了強制轉換的目標類型,expr是需要進行強制轉換的表達式。下面的程序說明了const_cast的用法:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. //#include <typeinfo.h>  
  3. void f(const int *p)  
  4. {  
  5.     int *v;  
  6.     //去掉const屬性  
  7.     v=const_cast<int*>(p);  
  8.     *v=100;  
  9. }  
  10. void main()    
  11. {    
  12.     cout<<"Start"<<endl;   
  13.       
  14.     int x=99;  
  15.     cout<<"x before call:"<<x<<endl;  
  16.     f(&x);  
  17.     cout<<"x after call:"<<x<<endl;  
  18.       
  19.     cout<<"End"<<endl;    
  20. }    
  21. //運行結果:  
  22. Start  
  23. x before call:99  
  24. x after call:100  
  25. End  
 

從程序中可以看到,雖然在函數f中參數被指定為一個const指針,但仍然可以修改變量x的值。需要強調的是:使用const_cast來去掉const屬性會帶來潛在的風險,所以應該謹慎的使用這個運算符。另外一點,只有const_cast才能去掉const屬性,也就是說,無論是dynamic_cast、reinterpret_cast還是static_cast都不能改變一個對象的const屬性。

static_cast

運算符static_cast執行的是非多態強制轉換。它可以被用在所有標准的強制轉換中,並且在執行強制轉換時不會做任何運行時的檢測。static_cast的通用形式如下:

static_cast<type>(expr);其中,type指定了強制轉換的目標類型,expr是需要進行強制轉換的表達式。從本質上來說,運算符static_cast就是用來替代傳統形式的強制轉換運算符,它執行的僅僅是一個非多態的強制轉換。例如:下面的代碼將float類型轉換為int類型:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. //#include <typeinfo.h>  
  3. void main()    
  4. {    
  5.     cout<<"Start"<<endl;   
  6.       
  7.     int i;  
  8.     float f=199.22f;  
  9.     i=static_cast<int>(f);  
  10.     cout<<i<<endl;  
  11.     cout<<"End"<<endl;    
  12. }    
  13. //運行結果:  
  14. Start  
  15. 199  
  16. End  
 

reinterpret_cast

reinterpret_cast可以將一種類型強制轉換為另一種不同的類型。例如:它可以將一個指針轉化為整數,或者將一個整數轉換為指針。它也可以用來轉換不相容的指針類型。reinterpret_cast的通用形式如下:

reinterpret_cast<type>(expr);其中,type指定了強制轉換的目標類型,expr是需要進行強制轉換的表達式。下面的程序說明了reinterpret_cast的用法:

[cpp]  view plain copy
  1. #include <iostream.h>  
  2. //#include <typeinfo.h>  
  3. void main()    
  4. {    
  5.     cout<<"Start"<<endl;   
  6.       
  7.     int i;  
  8.     char* p="This is a string";  
  9.     i=reinterpret_cast<int>(p);  
  10.     cout<<i<<endl;  
  11.     cout<<"End"<<endl;    
  12. }    
  13. //運行結果:  
  14. Start  
  15. 4378964  
  16. End  
 

在上面的程序中,強制轉換運算符reinterpret_cast將指針p強制轉換為一個整數。這種轉換代表最基本的類型變換,很好的說明了reinterpret_cast的用途。

深入

傳統的強制轉換與4種新的強制轉換

你可能想使用本章描述的強制轉換運算符來完全代替傳統形式的強制轉換。這種想法通常會帶來一個問題:“我應該使用傳統形式的強制轉換運算符還是這4種新的強制轉換運算符呢”。對於這個問題到目前為止還沒有一個所有程序員都認同的准則。因為,在從一種類型數據強制轉換為另一種類型數據時,新的強制轉換運算符能夠降低類型轉換中內在的危險性,因此許多C++程序員覺得應該使用新的強制轉換運算符。當然,這樣做是完全正確的。然而,其它的程序員則認為傳統形式的強制轉換已經被使用很多年,不應該輕易放棄。例如:有些人會認為在

簡單的以及相對安全的強制轉換中,傳統形式的強制轉換完全足夠。

      然而對於一點,所有程序員都沒有分歧;在執行多態類的強制轉換時,應該明確使用dynamic_cast.


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com 联系我们: