RTTI(Run-TimeTypeInformation,通過運行時類型信息)程式能夠使用基類的指針或引用來檢查這些指針或引用所指的對象的實際派生類型。
基本介紹
- 中文名:執行期型態訊息
- 外文名:Run-TimeTypeInformation
- 縮寫:RTTI
- 操作符:typeid操作符
- 所屬:生活
RTTI介紹,typeid函式,type_info類,typeid函式怎樣創建type_info類的對象,typeid函式的使用原理,typeid函式使用方式,強制類型轉換運算符,dynamic_cast強制轉換運算符,dynamic_cast的注意事項,typeid的注意事項,
RTTI介紹
RTTI提供了以下兩個非常有用的操作符:
(1)typeid操作符,返回指針和引用所指的實際類型。
(2)dynamic_cast操作符,將基類類型的指針或引用安全地轉換為派生類型的指針或引用。
面向對象的程式語言,象C++,Java,delphi都提供了對RTTI的支持。本文將簡略介紹RTTI的一些背景知識、描述RTTI的概念,並通過具體例子和代碼介紹什麼時候使用以及如何使用RTTI;本文還將詳細描述兩個重要的RTTI運算符的使用方法,它們是typeid和dynamic_cast。
其實,RTTI在C++中並不是什麼新的東西,它早在十多年以前就已經出現了。但是大多數開發人員,包括許多高層次的C++程式設計師對它並不怎么熟悉,更不用說使用RTTI來設計和編寫應用程式了。
一些面向對象專家在傳播自己的設計理念時,大多都主張在設計和開發中明智地使用虛擬成員函式,而不用RTTI機制。但是,在很多情況下,虛擬函式無法克服本身的局限。每每涉及到處理異類容器和根基類層次(如MFC)時,不可避免要對對象類型進行動態判斷,也就是動態類型的偵測。如何確定對象的動態類型呢?答案是使用內建的RTTI中的運算符:typeid和dynamic_cast。
在C++中存在虛函式,也就存在了多態性,對於多態性的對象,在程式編譯時可能會出現無法確定對象的類型的情況。當類中含有虛函式時,其基類的指針就可以指向任何派生類的對象,這時就有可能不知道基類指針到底指向的是哪個對象的情況,類型的確定要在運行時利用運行時類型標識做出。為了獲得一個對象的類型可以使用typeid函式,該函式反回一個對type_info類對象的引用,要使用typeid必須使用頭檔案,因為typeid是一個返回類型為typ_info的引用的函式所以這裡有必要先介紹一下type_info類
typeid函式
該函式的主要作用就是讓用戶知道當前的變數是什麼類型的,比如使用typeid(a).name()就能知道變數a是什麼類型的。因為typeid()函式是一個返回類型為consttypeid_info&類型的函式,所以下面先對type_info類作下介紹
type_info類
該類的具體實現方式依編譯器而定,但一般都有如下的成員定義
classtype_info
{private:
type_info(consttype_info&);
public:
virtual~type_info();//析構函式
booloperator!=(consttype_info&)const;//重載的!=運算符,以比較兩個對象的類型是否不相等
constchar*name()const;//使用得較多的成員函式name,該函式反回對象的類型的名字。前面使用的typeid(a).name()就調用了該成員函式
boolbefore(consttype_info&);};
因為type_info類的複製構造函式和賦值運算符都是私有的,所以不允許用戶自已創建type_info的對象,比如type_infoA;錯誤,沒有默認的構造函式。唯一要使用type_info類的方法就是使用typeid函式。
typeid函式怎樣創建type_info類的對象
該函式返回type_info類對象的引用,即形式為consttype_info&typeid();因此也可以說typeid函式是type_info類的一個引用對象,可以訪問type_info類的成員。但因為不能創建type_info類的對象,而typeid又必須返回一個類型為type_info類型的對象的引用,所以怎樣在typeid函式中創建一個type_info類的對象以便讓函式返回type_info類對象的引用就成了問題。這可能是把typid函式聲明為了type_info類的友元函式來實現的,默認構造函式是私有的並不能阻止該類的友元函式創建該類的對象。所以typeid函式如果是友元的話就可以訪問type_info類的私有成員,從而可以創建type_info類的對象,從而可以創建反回類型為type_info類的引用。舉個例子classA{private:A(){}A(constA&){}A&operator=(constA&){}friendA&f();};這裡把類A的默認構造函式,複製構造函式和賦值操作符定為私有從而防止創建類A的對象,但函式f()是類A的友元,所以在函式f()中可以創建類A的對象。同時為了實現函式f()返回的對象類型是A的引用,就必須在函式f中創建一個類A的對象以作為函式f的返回值,比如函式f可以這樣定義A&f(){Ama;cout<<”f”<
因為typeid函式是type_info類的對象,也就是說可以用該函式訪問type_info類的成員,即type_info類中重載的==和!=運算符,name()和before()成員函式,比如typid(a).name()和typid(a)==typid(b)等等。
typeid函式的使用原理
該函式的形式為type_info&typeid(object)其中object是任何類型的對象,可以是內置類型和用戶創建的類類型。可以看出typeid即是一個函式,同時他也是type_info類的對象,即typeid可以訪問類type_info類的成員,也可以做為一個單獨的函式來使用。做個簡單的例子,比如
classA{private:A(){b=3;cout<<”A”<默認構造函式
public:voidname(){cout<<”NA”<
friendAf();};//函式f()是類A的友元,因此在f中可以創建類A的對象。
Af()//函式f()在這裡即是類A的一個對象,也是一個單獨的函式。
{Am;//創建類A的對象,因為函式f是類A的友元,因此可以創建類A的對象
cout<<”F”<
main()
{f().name();//函式f()作為類A的對象使用,這裡要注意程式的執行順序,首先執行函式f()中的語句Am,因此調用類A的默認構造函式輸出A,然後執行Am;後面的語句,輸出F,再然後調用類A中的成員函式name輸出NA.
f();}//函式f()單獨作為函式使用。
我們創建一個類A,其中A的默認構造函式是私有的,也就是說不能用默認構造函式創建類A的對象。函式f()是類A的友元,且返回一個類A的對象,因為f()函式是類A的友元,所以在函式f中可以用默認構造函式創建類A的對象,這時函式f()同時是一個函式,也是類A的對象,因此也可以訪問類A中的成員。
typeid函式使用方式
2)、使用type_info類中重載的==與!=比較兩個對象的類型是否相等。使用該方法需要調用類type_info中重載的==和!=操作符,其使用方法為typid(object1)==typid(object2);如果兩個對象的類型相等則反回1,如果不相等則為0。這種使用方法通常用於比較兩個帶有虛函式的類的對象是否相等,比如有類A,其中定義有虛函式,而類B,類C,類D,都是從類A派生而來的且重定義了該虛函式,這時有兩個類A的指針p和p1,按照虛函式的原理,基類的指針可以指向任何派生類的對象,在這時就有可能需要比較兩個指針是否指向同一個對象,這時就可以這樣使用typeid了,typeid(*p)==typeid(*p1);這裡要注意的是typeid(*p)與typeid(p)是指的不同的對象類型,typeid(p)表示的是p的類型,在這裡p是一個指針,這個指針指向的是類A的對象,所以p的類型是A*,而typeid(*p)則不一樣,*p表示的是指針p實際所指的對象的類型,比如這裡的指針p指向派生類B,則typeid(*p)的類型為B。所以在測試兩個指針的類型是否是相等時應使用*p,即typeid(*p)==typeid(*p1)。如果是typeid(p)==typeid(p1)的話,則無論指針p和p1指向的什麼派生類對象,他們都是相等的,因為都是A*的類型。
強制類型轉換運算符
C++有四種強制類型轉換符,分別是dynamic_cast,const_cast,static_cast,reinterpret_cast。其中dynamic_cast與運行時類型轉換密切相關,在這裡我們介紹dynamic_cast。
dynamic_cast強制轉換運算符
該轉換符用於將一個指向派生類的基類指針或引用轉換為派生類的指針或引用,注意dynamic_cast轉換符只能用於含有虛函式的類,其表達式為dynamic_cast(表達式),其中的類型是指要將表達式轉換成的目標類型,比如含有虛函式的基類B和從基類B派生出的派生類D,則B*pb;D*pd,md;pb=&md;pd=dynamic(pb);最後一條語句表示把指向派生類D的基類指針pb轉換為派生類D的指針,然後將這個指針賦給派生類D的指針pd,有人可能會覺得這樣做沒有意義,既然指針pd要指向派生類為什麼不pd=&md;這樣做更直接呢?有些時候我們需要強制轉換,比如如果指向派生類的基類指針B想訪問派生類D中的除虛函式之外的成員時就需要把該指針轉換為指向派生類D的指針,以達到訪問派生類D中特有的成員的目的,比如派生類D中含有特有的成員函式g(),這時可以這樣來訪問該成員dynamic_cast(pb)->g();因為dynamic_cast轉換後的結果是一個指向派生類的指針,所以可以這樣訪問派生類中特有的成員。但是該語句不影響原來的指針的類型,即基類指針pb仍然是指向基類B的。如果單獨使用該指針仍然不能訪問派生類中特有的成員。一般情況下不推見這樣使用dynamic_cast轉換符,因為dynamic_cast的轉換並不會總是成功的,具體情況在後面介紹。
dynamic_cast的注意事項
dynamic_cast轉換符只能用於指針或者引用。dynamic_cast轉換符只能用於含有虛函式的類。dynamic_cast轉換操作符在執行類型轉換時首先將檢查能否成功轉換,如果能成功轉換則轉換之,如果轉換失敗,如果是指針則反回一個0值,如果是轉換的是引用,則拋出一個bad_cast異常,所以在使用dynamic_cast轉換之間應使用if語句對其轉換成功與否進行測試,比如pd=dynamic_cast(pb);if(pd){…}else{…},或者這樣測試if(dynamic_cast(pb)){…}else{…}。
typeid的注意事項
使用typeid要注意一個問題,那就是某些編譯器(如VisualC++)默認狀態是禁用RTTI的,目的是消除性能上的開銷。如果你的程式確實使用了RTTI,一定要記住在編譯前啟用RTTI。(vc6.0啟用方式:project->setting->c/c++->category->c++Language下面第二個複選框選中)。使用typeid可能產生一些將來的維護問題。假設你決定擴展上述的類層次,從MediaFile派生另一個叫LocalizeMedia的類,用這個類表示帶有不同語言說明文字的媒體檔案。但LocalizeMedia本質上還是個MediaFile類型的檔案。因此,當用戶在該類檔案圖示上單擊右鍵時,檔案管理器必須提供一個“播放”選單。可惜build()成員函式會調用失敗,原因是你沒有檢查這種特定的檔案類型。為了解決這個問題,你必須象下面這樣對build()打補丁:
voidmenu::build(constFile*pfile)
{
//......
elseif(typeid(*pfile)==typeid(LocalizedMedia))
{
add_option("play");
}
}
唉,這種做法真是顯得太業餘了,以後每次添加新的類,毫無疑問都必須打類似的補丁。顯然,這不是一個理想的解決方案。這個時候我們就要用到dynamic_cast,這個運算符用於多態編程中保證在運行時發生正確的轉換(即編譯器無法驗證是否發生正確的轉換)。用它來確定某個對象是MediaFile對象還是它的派生類對象。dynamic_cast常用於從多態編程基類指針向派生類指針的向下類型轉換。它有兩個參數:一個是類型名;另一個是多態對象的指針或引用。其功能是在運行時將對象強制轉換為目標類型並返回布爾型結果。也就是說,如果該函式成功地並且是動態的將*pfile強制轉換為MediaFile,那么pfile的動態類型是MediaFile或者是它的派生類。否則,pfile則為其它的類型:
voidmenu::build(constFile*pfile)
{
if(dynamic_cast(pfile))
{
//pfile是MediaFile或者是MediaFile的派生類LocalizedMedia
add_option("play");
}
elseif(dynamic_cast(pfile))
{
//pfile是TextFile是TextFile的派生類
add_option("edit");
}
}
細細想一下,雖然使用dynamic_cast確實很好地解決了我們的問題,但也需要我們付出代價,那就是與typeid相比,dynamic_cast不是一個常量時間的操作。為了確定是否能完成強制類型轉換,dynamic_cast`必須在運行時進行一些轉換細節操作。因此在使用dynamic_cast操作時,應該權衡對性能的影響。