生活随笔
收集整理的這篇文章主要介紹了
RTTI-运行时类型识别
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
RTTI
?編輯
RTTI(Run-Time Type Information),通過運(yùn)行時(shí)類型信息程序能夠使用基類的指針或引用來檢查這些指針或引用所指的對象的實(shí)際派生類型。
中文名
RTTI外文名
Run-Time Type Information 屬????于
程序函????數(shù)
typeid
目錄
1?RTTI介紹 ??typeid函數(shù) ??type_info類 ??typeid函數(shù)怎樣創(chuàng)建type_info類的對象 ??typeid函數(shù)的使用原理 ??typeid函數(shù)使用方式 ??強(qiáng)制類型轉(zhuǎn)換運(yùn)算符 ??dynamic_cast強(qiáng)制轉(zhuǎn)換運(yùn)算符 ??dynamic_cast的注意事項(xiàng) 2?typeid的注意事項(xiàng)
RTTI介紹編輯
RTTI提供了以下兩個(gè)非常有用的操作符:
(1)typeid操作符,返回指針和引用所指的實(shí)際類型。
(2)dynamic_cast操作符,將基類類型的指針或引用安全地轉(zhuǎn)換為派生類型的指針或引用。
面向?qū)ο蟮木幊陶Z言,象C++,Java,delphi都提供了對RTTI的支持。 本文將簡略介紹 RTTI 的一些背景知識、描述 RTTI 的概念,并通過具體例子和代碼介紹什么時(shí)候使用以及如何使用 RTTI;本文還將詳細(xì)描述兩個(gè)重要的 RTTI 運(yùn)算符的使用方法,它們是 typeid 和dynamic_cast。
其實(shí),RTTI 在C++中并不是什么新的東西,它早在十多年以前就已經(jīng)出現(xiàn)了。但是大多數(shù)開發(fā)人員,包括許多高層次的C++程序員對它并不怎么熟悉,更不用說使用 RTTI 來設(shè)計(jì)和編寫應(yīng)用程序了。
一些面向?qū)ο髮<以趥鞑プ约旱脑O(shè)計(jì)理念時(shí),大多都主張?jiān)谠O(shè)計(jì)和開發(fā)中明智地使用虛擬成員函數(shù),而不用 RTTI 機(jī)制。但是,在很多情況下,虛擬函數(shù)無法克服本身的局限。每每涉及到處理異類容器和根基類層次(如 MFC)時(shí),不可避免要對對象類型進(jìn)行動(dòng)態(tài)判斷,也就是動(dòng)態(tài)類型的偵測。如何確定對象的動(dòng)態(tài)類型呢?答案是使用內(nèi)建的 RTTI 中的運(yùn)算符:typeid 和 dynamic_cast。
在C++中存在虛函數(shù),也就存在了多態(tài)性,對于多態(tài)性的對象,在程序編譯時(shí)可能會(huì)出現(xiàn)無法確定對象的類型的情況。當(dāng)類中含有虛函數(shù)時(shí),其基類的指針就可以指向任何派生類的對象,這時(shí)就有可能不知道基類指針到底指向的是哪個(gè)對象的情況,類型的確定要在運(yùn)行時(shí)利用運(yùn)行時(shí)類型標(biāo)識做出。為了獲得一個(gè)對象的類型可以使用typeid函數(shù),該函數(shù)反回一個(gè)對type_info類對象的引用,要使用typeid必須使用頭文件<typeinfo>,因?yàn)閠ypeid是一個(gè)返回類型為typ_info的引用的函數(shù)所以這里有必要先介紹一下type_info類
typeid函數(shù)
該函數(shù)的主要作用就是讓用戶知道當(dāng)前的變量是什么類型的,比如使用typeid(a).name()就能知道變量a是什么類型的。因?yàn)閠ypeid()函數(shù)是一個(gè)返回類型為const typeid_info&類型的函數(shù),所以下面先對type_info類作下介紹
type_info類
該類的具體實(shí)現(xiàn)方式依編譯器而定,但一般都有如下的成員定義
| 1 2 3 4 5 6 7 8 9 10 11 12 | classtype_info { private: type_info(consttype_info&); type_info&operator=(consttype_info&);//type_info類的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符是私有的。 public: virtual~type_info();//析構(gòu)函數(shù) booloperator==(consttype_info&)const;//在type_info類中重載了==運(yùn)算符,該運(yùn)算符可以比較兩個(gè)對象的類型是否相等。 booloperator!=(consttype_info&)const;//重載的!=運(yùn)算符,以比較兩個(gè)對象的類型是否不相等 constchar*name()const;//使用得較多的成員函數(shù)name,該函數(shù)反回對象的類型的名字。前面使用的typeid(a).name()就調(diào)用了該成員函數(shù) boolbefore(consttype_info&); }; |
因?yàn)閠ype_info類的復(fù)制構(gòu)造函數(shù)和賦值運(yùn)算符都是私有的,所以不允許用戶自已創(chuàng)建type_info的對象,比如type_info A;錯(cuò)誤,沒有默認(rèn)的構(gòu)造函數(shù)。唯一要使用type_info類的方法就是使用typeid函數(shù)。
typeid函數(shù)怎樣創(chuàng)建type_info類的對象
該函數(shù)返回type_info類對象的引用,即形式為const type_info& typeid();因此也可以說typeid函數(shù)是type_info類的一個(gè)引用對象,可以訪問type_info類的成員。但因?yàn)椴荒軇?chuàng)建type_info類的對象,而typeid又必須返回一個(gè)類型為type_info類型的對象的引用,所以怎樣在typeid函數(shù)中創(chuàng)建一個(gè)type_info類的對象以便讓函數(shù)返回type_info類對象的引用就成了問題。這可能是把typid函數(shù)聲明為了type_info類的友元函數(shù)來實(shí)現(xiàn)的,默認(rèn)構(gòu)造函數(shù)是私有的并不能阻止該類的友元函數(shù)創(chuàng)建該類的對象。所以typeid函數(shù)如果是友元的話就可以訪問type_info類的私有成員,從而可以創(chuàng)建type_info類的對象,從而可以創(chuàng)建反回類型為type_info類的引用。
舉個(gè)例子:
class A{private:A(){} A(const A&){} A& operator =(const A&){} friend A& f();};這里把類A的默認(rèn)構(gòu)造函數(shù),復(fù)制構(gòu)造函數(shù)和賦值操作符定為私有從而防止創(chuàng)建類A的對象,但函數(shù)f()是類A的友元,所以在函數(shù)f()中可以創(chuàng)建類A的對象。同時(shí)為了實(shí)現(xiàn)函數(shù)f()返回的對象類型是A的引用,就必須在函數(shù)f中創(chuàng)建一個(gè)類A的對象以作為函數(shù)f的返回值,比如函數(shù)f可以這樣定義A& f(){A ma; cout<<”f”<<endl; return ma}。
因?yàn)閠ypeid函數(shù)是type_info類的對象,也就是說可以用該函數(shù)訪問type_info類的成員,即type_info類中重載的= =和!=運(yùn)算符,name()和before()成員函數(shù),比如typid(a).name()和typid(a) == typid(b)等等。
typeid函數(shù)的使用原理
該函數(shù)的形式為type_info& typeid(object)其中object是任何類型的對象,可以是內(nèi)置類型和用戶創(chuàng)建的類類型??梢钥闯鰐ypeid即是一個(gè)函數(shù),同時(shí)他也是type_info類的對象,即typeid可以訪問類type_info類的成員,也可以做為一個(gè)單獨(dú)的函數(shù)來使用。做個(gè)簡單的例子,比如
class A{private: A(){b=3;cout<<”A”<<endl;} //私有的默認(rèn)構(gòu)造函數(shù)
public: void name(){cout<<”NA”<<endl;} int b;
friend A f();}; //函數(shù)f()是類A的友元,因此在f中可以創(chuàng)建類A的對象。
A f() //函數(shù)f()在這里即是類A的一個(gè)對象,也是一個(gè)單獨(dú)的函數(shù)。
{ A m; //創(chuàng)建類A的對象,因?yàn)楹瘮?shù)f是類A的友元,因此可以創(chuàng)建類A的對象
cout<<”F”<<endl; return m;}
main()
{ f().name(); //函數(shù)f()作為類A的對象使用,這里要注意程序的執(zhí)行順序,首先執(zhí)行函數(shù)f()中的語句A m,因此調(diào)用類A的默認(rèn)構(gòu)造函數(shù)輸出A,然后執(zhí)行A m;后面的語句,輸出F,再然后調(diào)用類A中的成員函數(shù)name輸出NA.
f(); } //函數(shù)f()單獨(dú)作為函數(shù)使用。
我們創(chuàng)建一個(gè)類A,其中A的默認(rèn)構(gòu)造函數(shù)是私有的,也就是說不能用默認(rèn)構(gòu)造函數(shù)創(chuàng)建類A的對象。函數(shù)f()是類A的友元,且返回一個(gè)類A的對象,因?yàn)閒()函數(shù)是類A的友元,所以在函數(shù)f中可以用默認(rèn)構(gòu)造函數(shù)創(chuàng)建類A的對象,這時(shí)函數(shù)f()同時(shí)是一個(gè)函數(shù),也是類A的對象,因此也可以訪問類A中的成員。
typeid函數(shù)使用方式
、使用type_info類中的name()成員函數(shù)反回對象的類型的名稱。其方法為:typeid(object).name()其中object是要顯示其相應(yīng)類型名的對象,該函數(shù)反回的名字因編譯器而定。這里要注意的就是使用方式一中提到的虛函數(shù)類型的問題,即如果有類A,且有虛函數(shù),類B,C,D都是從類A派生的,且都重定義了類A中的虛函數(shù),這時(shí)有類A的指針p,再把對象類B的對象的地址賦給指針p,則typeid(p).name()將反回的類型將是A*,因?yàn)檫@里的p表示的是一個(gè)指針,該指針是類型為A的指針,所以返回A*,而typeid(*p).name()將返回B,因?yàn)橹羔榩是指向類B的對象的,而*p就表示的是類B的對象,所以返回B。
2)、使用type_info類中重載的= =與!=比較兩個(gè)對象的類型是否相等。使用該方法需要調(diào)用類type_info中重載的= =和!=操作符,其使用方法為typid(object1)= =typid(object2);如果兩個(gè)對象的類型相等則返回1,如果不相等則為0。這種使用方法通常用于比較兩個(gè)帶有虛函數(shù)的類的對象是否相等,比如有類A,其中定義有虛函數(shù),而類B,類C,類D,都是從類A派生而來的且重定義了該虛函數(shù),這時(shí)有兩個(gè)類A的指針p和p1,按照虛函數(shù)的原理,基類的指針可以指向任何派生類的對象,在這時(shí)就有可能需要比較兩個(gè)指針是否指向同一個(gè)對象,這時(shí)就可以這樣使用typeid了,typeid(*p)= =typeid(*p1);這里要注意的是typeid(*p)與typeid(p)是指的不同的對象類型,typeid(p)表示的是p的類型,在這里p是一個(gè)指針,這個(gè)指針指向的是類A的對象,所以p的類型是A*,而typeid(*p)則不一樣,*p表示的是指針p實(shí)際所指的對象的類型,比如這里的指針p指向派生類B,則typeid(*p)的類型為B。所以在測試兩個(gè)指針的類型是否是相等時(shí)應(yīng)使用*p,即typeid(*p)= =typeid(*p1)。如果是typeid(p)= =typeid(p1)的話,則無論指針p和p1指向的什么派生類對象,他們都是相等的,因?yàn)槎际茿 *的類型。
強(qiáng)制類型轉(zhuǎn)換運(yùn)算符
C++有四種強(qiáng)制類型轉(zhuǎn)換符,分別是dynamic_cast,const_cast,static_cast,reinterpret_cast。其中dynamic_cast與運(yùn)行時(shí)類型轉(zhuǎn)換密切相關(guān),在這里我們介紹dynamic_cast。
dynamic_cast強(qiáng)制轉(zhuǎn)換運(yùn)算符
該轉(zhuǎn)換符用于將一個(gè)指向派生類的基類指針或引用轉(zhuǎn)換為派生類的指針或引用,注意dynamic_cast轉(zhuǎn)換符只能用于含有虛函數(shù)的類,其表達(dá)式為dynamic_cast<類型>(表達(dá)式),其中的類型是指要將表達(dá)式轉(zhuǎn)換成的目標(biāo)類型,比如含有虛函數(shù)的基類B和從基類B派生出的派生類D,則B *pb; D *pd, md; pb=&md; pd=dynamic<D*>(pb); 最后一條語句表示把指向派生類D的基類指針pb轉(zhuǎn)換為派生類D的指針,然后將這個(gè)指針賦給派生類D的指針pd,有人可能會(huì)覺得這樣做沒有意義,既然指針pd要指向派生類為什么不pd=&md;這樣做更直接呢?有些時(shí)候我們需要強(qiáng)制轉(zhuǎn)換,比如如果指向派生類的基類指針B想訪問派生類D中的除虛函數(shù)之外的成員時(shí)就需要把該指針轉(zhuǎn)換為指向派生類D的指針,以達(dá)到訪問派生類D中特有的成員的目的,比如派生類D中含有特有的成員函數(shù)g(),這時(shí)可以這樣來訪問該成員dynamic_cast<D*>(pb)->g();因?yàn)閐ynamic_cast轉(zhuǎn)換后的結(jié)果是一個(gè)指向派生類的指針,所以可以這樣訪問派生類中特有的成員。但是該語句不影響原來的指針的類型,即基類指針pb仍然是指向基類B的。如果單獨(dú)使用該指針仍然不能訪問派生類中特有的成員。一般情況下不推薦這樣使用dynamic_cast轉(zhuǎn)換符,因?yàn)閐ynamic_cast的轉(zhuǎn)換并不會(huì)總是成功的,具體情況在后面介紹。
dynamic_cast的注意事項(xiàng)
dynamic_cast轉(zhuǎn)換符只能用于指針或者引用。dynamic_cast轉(zhuǎn)換符只能用于含有虛函數(shù)的類。dynamic_cast轉(zhuǎn)換操作符在執(zhí)行類型轉(zhuǎn)換時(shí)首先將檢查能否成功轉(zhuǎn)換,如果能成功轉(zhuǎn)換則轉(zhuǎn)換之,如果轉(zhuǎn)換失敗,如果是指針則反回一個(gè)0值,如果是轉(zhuǎn)換的是引用,則拋出一個(gè)bad_cast異常,所以在使用dynamic_cast轉(zhuǎn)換之間應(yīng)使用if語句對其轉(zhuǎn)換成功與否進(jìn)行測試,比如pd=dynamic_cast<D*>(pb); if(pd){…}else{…},或者這樣測試if(dynamic_cast<D*>(pb)){…}else{…}。
typeid的注意事項(xiàng)編輯
使用 typeid 要注意一個(gè)問題,那就是某些編譯器(如?Visual C++)默認(rèn)狀態(tài)是禁用 RTTI 的,目的是消除性能上的開銷。如果你的程序確實(shí)使用了 RTTI,一定要記住在編譯前啟用 RTTI。(vc6.0啟用方式:project->setting->c/c++->category->c++ Language 下面第二個(gè)復(fù)選框選中)。使用 typeid 可能產(chǎn)生一些將來的維護(hù)問題。假設(shè)你決定擴(kuò)展上述的類層次,從MediaFile 派生另一個(gè)叫 LocalizeMedia 的類,用這個(gè)類表示帶有不同語言說明文字的媒體文件。但 LocalizeMedia 本質(zhì)上還是個(gè) MediaFile 類型的文件。因此,當(dāng)用戶在該類文件圖標(biāo)上單擊右鍵時(shí),文件管理器必須提供一個(gè)“播放”菜單。可惜 build()成員函數(shù)會(huì)調(diào)用失敗,原因是你沒有檢查這種特定的文件類型。為了解決這個(gè)問題,你必須象下面這樣對 build() 打補(bǔ)丁:
| 1 2 3 4 5 6 7 8 | voidmenu::build(constFile*pfile) { //...... elseif(typeid(*pfile)==typeid(LocalizedMedia)) { add_option("play"); } } |
唉,這種做法真是顯得太業(yè)余了,以后每次添加新的類,毫無疑問都必須打類似的補(bǔ)丁。顯然,這不是一個(gè)理想的解決方案。這個(gè)時(shí)候我們就要用到?dynamic_cast,這個(gè)運(yùn)算符用于多態(tài)編程中保證在運(yùn)行時(shí)發(fā)生正確的轉(zhuǎn)換(即編譯器無法驗(yàn)證是否發(fā)生正確的轉(zhuǎn)換)。用它來確定某個(gè)對象是 MediaFile 對象還是它的派生類對象。dynamic_cast 常用于從多態(tài)編程基類指針向派生類指針的向下類型轉(zhuǎn)換。它有兩個(gè)參數(shù):一個(gè)是類型名;另一個(gè)是多態(tài)對象的指針或引用。其功能是在運(yùn)行時(shí)將對象強(qiáng)制轉(zhuǎn)換為目標(biāo)類型并返回布爾型結(jié)果。也就是說,如果該函數(shù)成功地并且是動(dòng)態(tài)的將 *pfile 強(qiáng)制轉(zhuǎn)換為 MediaFile,那么 pfile的動(dòng)態(tài)類型是 MediaFile 或者是它的派生類。否則,pfile 則為其它的類型:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | voidmenu::build(constFile*pfile) { if(dynamic_cast<MediaFile*>(pfile)) { //pfile是MediaFile或者是MediaFile的派生類LocalizedMedia add_option("play"); } elseif(dynamic_cast<TextFile*>(pfile)) { //pfile是TextFile是TextFile的派生類 add_option("edit"); } } |
細(xì)細(xì)想一下,雖然使用?dynamic_cast?確實(shí)很好地解決了我們的問題,但也需要我們付出代價(jià),那就是與 typeid 相比,dynamic_cast 不是一個(gè)常量時(shí)間的操作。為了確定是否能完成強(qiáng)制類型轉(zhuǎn)換,dynamic_cast`必須在運(yùn)行時(shí)進(jìn)行一些轉(zhuǎn)換細(xì)節(jié)操作。因此在使用 dynamic_cast 操作時(shí),應(yīng)該權(quán)衡對性能的影響。
總結(jié)
以上是生活随笔為你收集整理的RTTI-运行时类型识别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。