RTTI(Runtime Type Information )

RTTI 是「Runtime Type Information」的縮寫,意思是:運行時類型信息。它提供了運行時肯定對象類型的方法。本文將簡略介紹 RTTI 的一些背景知識、描述 RTTI 的概念,並經過具體例子和代碼介紹何時使用以及如何使用 RTTI;本文還將詳細描述兩個重要的 RTTI 運算符的使用方法,它們是 typeid 和 dynamic_cast。 
其實,RTTI 在C++中並非什麼新的東西,它早在十多年之前就已經出現了。可是大多數開發人員,包括許多高層次的C++程序員對它並不怎麼熟悉,更不用說使用 RTTI 來設計和編寫應用程序了。 
一些面向對象專家在傳播本身的設計理念時,大多都主張在設計和開發中明智地使用虛擬成員函數,而不用 RTTI 機制。可是,在不少狀況下,虛擬函數沒法克服自己的侷限。往往涉及處處理異類容器和根基類層次(如 MFC)時,不可避免要對對象類型進行動態判斷,也就是動態類型的偵測。如何肯定對象的動態類型呢?答案是使用內建的 RTTI 中的運算符:typeid 和 dynamic_cast。 
首先讓咱們來設計一個類層次,假設咱們建立了某個處理文件的抽象基類。它聲明下列純虛擬函數:open()、close()、read()和 write(): 

class File 

public: 
virtual int open(const string & filename)=0; 
virtual int close(const string & filename)=0; 
// 
virtual ~File()=0; // 記住添加純虛擬析構函數(dtor) 
}; 
如今從 File 類派生的類要實現基類的純虛擬函數,同時還要提供一些其餘的操做。假設派生類爲 DiskFile,除了實現基類的純虛擬函數外,還要實現本身的flush()和defragment()操做: class DiskFile: public File 

public: 
int open(const string & filename); 

// 實現其餘的純虛擬函數 
...... 

// 本身的專有操做 
virtual int flush(); 
virtual int defragment(); 
}; 
接着,又從 DiskFile 類派生兩個類,假設爲 TextFile 和 MediaFile。前者針對文本文件,後者針對音頻和視頻文件: class TextFile: public DiskFile 

// ...... 
int sort_by_words(); 
}; 

class MediaFile: public DiskFile 

//...... 
}; 
咱們之因此要建立這樣的類層次,是由於這樣作之後能夠建立多態對象,如:File *pfile; // *pfile的靜態類型是 File 
if(some_condition) 
pfile = new TextFile; // 動態類型是 TextFile 
else 
pfile = new DiskFile; // 動態類型是 DiskFile 
假設你正在開發一個基於圖形用戶界面(GUI)的文件管理器,每一個文件均可以以圖標方式顯示。當鼠標移到圖標上並單擊右鍵時,文件管理器打開一個菜單,每一個文件除了共同的菜單項,不一樣的文件類型還有不一樣的菜單項。如:共同的菜單項有「打開」「拷貝」、和「粘貼」,此外,還有一些針對特殊文件的專門操做。好比,文本文件會有「編輯」操做,而多媒體文件則會有「播放」菜單。爲了使用 RTTI 來動態定製菜單,文件管理器必須偵測每一個文件的動態類型。利用 運算符 typeid 能夠獲取與某個對象關聯的運行時類型信息。typeid 有一個參數,傳遞對象或類型名。所以,爲了肯定 x 的動態類型是否是Y,能夠用表達式:typeid(x) == typeid(Y)實現:#include <typeinfo> // typeid 須要的頭文件 
void menu::build(const File * pfile) 

if (typeid(*pfile)==typeid(TextFile)) 

add_option("edit"); 

else if (typeid(*pfile)==typeid(MediaFile)) 

add_option("play"); 


使用 typeid 要注意一個問題,那就是某些編譯器(如 Visual C++)默認狀態是禁用 RTTI 的,目的是消除性能上的開銷。若是你的程序確實使用了 RTTI,必定要記住在編譯前啓用 RTTI。使用 typeid 可能產生一些未來的維護問題。假設你決定擴展上述的類層次,從MediaFile 派生另外一個叫 LocalizeMedia 的類,用這個類表示帶有不一樣語言說明文字的媒體文件。但 LocalizeMedia 本質上仍是個 MediaFile 類型的文件。所以,當用戶在該類文件圖標上單擊右鍵時,文件管理器必須提供一個「播放」菜單。惋惜 build()成員函數會調用失敗,緣由是你沒有檢查這種特定的文件類型。爲了解決這個問題,你必須象下面這樣對 build() 打補丁: void menu::build(const File * pfile) 


//...... 

else if (typeid(*pfile)==typeid(LocalizedMedia)) 

add_option("play"); 


唉,這種作法真是顯得太業餘了,之後每次添加新的類,毫無疑問都必須打相似的補丁。顯然,這不是一個理想的解決方案。這個時候咱們就要用到 dynamic_cast,這個運算符用於多態編程中保證在運行時發生正確的轉換(即編譯器沒法驗證是否發生正確的轉換)。用它來肯定某個對象是 MediaFile 對象仍是它的派生類對象。dynamic_cast 經常使用於從多態編程基類指針向派生類指針的向下類型轉換。它有兩個參數:一個是類型名;另外一個是多態對象的指針或引用。其功能是在運行時將對象強制轉換爲目標類型並返回布爾型結果。也就是說,若是該函數成功地而且是動態的將 *pfile 強制轉換爲 MediaFile,那麼 pfile的動態類型是 MediaFile 或者是它的派生類。不然,pfile 則爲其它的類型:void menu::build(const File * pfile) 

if (dynamic_cast <MediaFile *> (pfile)) 

// pfile 是 MediaFile 或者是MediaFile的派生類 LocalizedMedia 
add_option("play"); 

else if (dynamic_cast <TextFile*> (pfile)) 

// pfile 是 TextFile 是TextFile的派生類 
add_option("edit"); 


細細想一下,雖然使用 dynamic_cast 確實很好地解決了咱們的問題,但也須要咱們付出代價,那就是與 typeid 相比,dynamic_cast 不是一個常量時間的操做。爲了肯定是否能完成強制類型轉換,dynamic_cast`必須在運行時進行一些轉換細節操做。所以在使用 dynamic_cast 操做時,應該權衡對性能的影響html

第二章c++

typeid和RTTI C++- -程序員

Tag: typeid和RTTI    C++                                          算法

觀點有一些值得商榷的地方編程

關於typeid和RTTI的問答 
問:在c++裏怎麼能知道一個變量的具體類型,如:c#裏的typeof.還有我怎麼知道一個變量的類型是某個類型的子類,也就是實現關鍵字ISc#

答:
1。運行時獲知變量類型名稱,可使用 typeid(變量).name,須要注意不是全部編譯器都輸出"int"、"float"等之類的名稱,對於這類的編譯器能夠這樣使用:float f = 1.1f; if( typeid(f) == typeid(0.0f) ) ……
2。對於多態類實例,想獲得實際的類名稱,須要使用到RTTI,這須要在編譯的時候加上參數"/GR"。
3。對於普通變量,既然是本身寫的,那固然也就應該知道它的類型,其實用不着運行時獲知;對於多態類實例,既然須要運行時獲知實際類型,那麼就說明這裏不具備多態性,既然沒有多態性就不該該抽象它,這屬於設計錯誤,總之,我認爲RTTI是多餘的。
4。對於多態類實例,使用 typeid(value) == typeid(value)來判斷,不如使用 dynamic_cast 來判斷,它們的原理是同樣的。數組

事例代碼:
#include 
using namespace std;
int main( void )
{
// sample 1
    cout << typeid(1.1f).name() << endl;
// sample 2
    class Base1
    {
    };
    class Derive1 : public Base1
    {
    };
    Derive1 d1;
    Base1& b1 = d1;
    cout << typeid(b1).name() << endl; // 輸出"class Base1",由於Derive1和Base1之間沒有多態性
// sample 3, 編譯時須要加參數 /GR
    class Base2
    {
        virtual void fun( void ) {}
    };
    class Derive2 : public Base2
    {
    };
    Derive2 d2;
    Base2& b2 = d2;
    cout << typeid(b2).name() << endl; // 輸出"class Derive2",由於Derive1和Base1之間有了多態性
// sample 4
    class Derive22 : public Base2
    {
    };
    // 指針強制轉化失敗後能夠比較指針是否爲零,而引用卻沒辦法,因此引用制轉化失敗後拋出異常
    Derive2* pb1 = dynamic_cast(&b2);
    cout << boolalpha << (0!=pb1) << endl; // 輸出"true",由於b2自己就確實是Derive2
    Derive22* pb2 = dynamic_cast(&b2);
    cout << boolalpha << (0!=pb2) << endl; // 輸出"true",由於b2自己不是Derive2安全

    try {
        Derive2& rb1 = dynamic_cast<DERIVE2& />(b2);
        cout << "true" << endl;
    } catch( bad_cast )
    {
        cout << "false" << endl;
    }
    try {
        Derive22& rb2 = dynamic_cast<DERIVE22& />(b2);
        cout << "true" << endl;
    } catch( ... ) // 應該是 bad_cast,但不知道爲何在VC++6.0中卻不行
    {
        cout << "false" << endl;
    }數據結構

    return 0;
}app

posted on 2004-09-13 22:45 周星星 閱讀(2278) 評論(9)  編輯 收藏


/////////////////////////////////////////////
MFC六大關鍵技術之運行時類型識別
運行時類型識別(RTTI)便是程序執行過程當中知道某個對象屬於某個類,咱們平時用C++編程接觸的RTTI通常是編譯器的RTTI

運行時類型識別(RTTI)便是程序執行過程當中知道某個對象屬於某個類,咱們平時用C++編程接觸的RTTI通常是編譯器的RTTI,便是在新版本的VC++編譯器裏面選用「使能RTTI」,而後載入typeinfo.h文件,就可使用一個叫typeid()的運算子,它的地位與在C++編程中的sizeof()運算子相似的地方(包含一個頭文件,而後就有一個熟悉好用的函數)。typdid()關鍵的地方是能夠接受兩個類型的參數:一個是類名稱,一個是對象指針。因此咱們判別一個對象是否屬於某個類就能夠象下面那樣:

if (typeid (ClassName)== typeid(*ObjectName)){
((ClassName*)ObjectName)->Fun();
}

  象上面所說的那樣,一個typeid()運算子就能夠輕鬆地識別一個對象是否屬於某一個類,但MFC並非用typeid()的運算子來進行動態類型識別,而是用一大堆使人費解的宏。不少學員在這裏很疑惑,好象MFC在大部分地方都是故做神祕。使們你們編程時很迷惘,只知道在這裏加入一組宏,又在那兒加入一個映射,而不知道咱們爲何要加入這些東東。

  其實,早期的MFC並無typeid()運算子,因此只能沿用一個老辦法。咱們甚至能夠想象一下,若是MFC早期就有template(模板)的概念,可能更容易解決RTTI問題。

  因此,咱們要回到「古老」的年代,想象一下,要完成RTTI要作些什麼事情。就好像咱們在一個新型(新型到咱們還不認識)電器公司裏面,咱們要識別哪一個是電飯鍋,哪一個是電磁爐等等,咱們要查看登記的各電器一系列的信息,咱們才能夠比較、鑑別,那個東西是什麼!
要登記一系列的消息並非一件簡單的事情,你們可能首先想到用數組登記對象。但若是用數組,咱們要定義多大的數組纔好呢,大了浪費空間,小了更加不行。因此咱們要用另外一種數據結構——鏈表。由於鏈表理論上可大可小,能夠無限擴展。

  鏈表是一種經常使用的數據結構,簡單地說,它是在一個對象裏面保存了指向下一個同類型對象的指針。咱們大致能夠這樣設計咱們的類:

struct CRuntimeClass
{
……類的名稱等一切信息……
CRuntimeClass * m_pNextClass;//指向鏈表中下一CRuntimeClass對象的指針
};

  鏈表還應該有一個表頭和一個表尾,這樣咱們在查鏈表中各對象元素的信息的時候才知道從哪裏查起,到哪兒結束。咱們還要註明自己是由哪能個類派生。因此咱們的鏈表類要這樣設計:

struct CRuntimeClass
{
……類的名稱等一切信息……
CRuntimeClass * m_pBaseClass;//指向所屬的基類。
CRuntimeClass * m_pNextClass;//定義表尾的時候只要定義此指針爲空就能夠 了。
static CRuntimeClass* pFirstClass;//這裏表頭指針屬於靜態變量,由於咱們知道static變量在內存中只初始化一次,就能夠爲各對象所用!保證了各對象只有一個表頭。
};

  有了CRuntimeClass結構後,咱們就能夠定義鏈表了:

static CRuntimeClass classCObject={NULL,NULL};//這裏定義了一個CRuntimeClass對象,

  由於classCObject無基類,因此m_pBaseClass爲NULL。由於目前只有一個元素(即目前沒有下一元素),因此m_pNextClass爲NULL(表尾)。

  至於pFirstClass(表頭),你們可能有點想不通,它到什麼地方去了。由於咱們這裏並不想把classCObject做爲鏈表表頭,咱們還要在前面插入不少的CRuntimeClass對象,而且由於pFirstClass爲static指針,便是說它不是屬於某個對象,因此咱們在用它以前要先初始化:

CRuntimeClass* CRuntimeClass::pFirstClass=NULL;

  如今咱們能夠在前面插入一個CRuntimeClass對象,插入以前我得重要申明一下:若是單純爲了運行時類型識別,咱們未必用到m_pNextClass指針(更可能是在運行時建立時用),咱們關心的是類自己和它的基類。這樣,查找一個對象是否屬於一個類時,主要關心的是類自己及它的基類:

CRuntimeClass classCCmdTarget={ &classCObject, NULL};
CRuntimeClass classCWnd={ &classCCmdTarget ,NULL };
CRuntimeClass classCView={ &classCWnd , NULL };

  好了,上面只是僅僅爲一個指針m_pBaseClass賦值(MFC中真正CRuntimeClass有多個成員變量和方法),就鏈接成了鏈表。假設咱們如今已所有構造完成本身須要的CRuntimeClass對象,那麼,這時候應該定義表頭。即要用pFirstClass指針指向咱們最後構造的CRuntimeClass對象——classCView。

CRuntimeClass::pFirstClass=&classCView;

  如今鏈表有了,表頭表尾都完善了,問題又出現了,咱們應該怎樣訪問每個CRuntimeClass對象?要判斷一個對象屬於某類,咱們要從表頭開始,一直向表尾查找到表尾,而後才能比較得出結果嗎。確定不是這樣!

  你們能夠這樣想一下,咱們構造這個鏈表的目的,就是構造完以後,可以按主觀地拿一個CRuntimeClass對象和鏈表中的元素做比較,看看其中一個對象中否屬於你指定的類。這樣,咱們須要有一個函數,一個能返回自身類型名的函數GetRuntimeClass()。

  上面簡單地說一下鏈表的過程,但單純有這個鏈表是沒有任何意義。回到MFC中來,咱們要實現的是在每一個須要有RTTI能力的類中構造一個CRuntimeClass對象,比較一個類是否屬於某個對象的時候,實際上只是比較CRuntimeClass對象。

  如何在各個類之中插入CRuntimeClass對象,而且指定CRuntimeClass對象的內容及CRuntimeClass對象的連接,這裏起碼有十行的代碼才能完成。在每一個須要有RTTI能力的類設計中都要重複那十多行代碼是一件乏味的事情,也容易出錯,因此MFC用了兩個宏代替這些工做,即DECLARE_DYNAMIC(類名)和IMPLEMENT_DYNAMIC(類名,基類名)。從這兩個宏咱們能夠看出在MFC名類中的CRuntimeClass對象構造鏈接只有類名及基類名的不一樣!

  到此,可能會有朋友問:爲何要用兩個宏,用一個宏不能夠代換CRuntimeClass對象構造鏈接嗎?我的認爲確定能夠,由於宏只是文字代換的遊戲而已。但咱們在編程之中,頭文件與源文件是分開的,咱們要在頭文件頭聲明變量及方法,在源文件裏實具體實現。便是說咱們要在頭文件中聲明:

public:
static CRuntimeClass classXXX //XXX爲類名
virtual CRuntime* GetRuntimeClass() const;

  而後在源文件裏實現:

CRuntimeClass* XXX::classXXX={……};
CRuntime* GetRuntimeClass() const;

{ return &XXX:: classXXX;}//這裏不能直接返回&classXXX,由於static變量是類擁有而不是對象擁有。

  咱們一眼能夠看出MFC中的DECLARE_DYNAMIC(類名)宏應該這樣定義:

#define DECLARE_DYNAMIC(class_name) public: static CRuntimeClass class##class_name; 
virtual CRuntimeClass* GetRuntimeClass() const;

  其中##爲鏈接符,可讓咱們傳入的類名前面加上class,不然跟原類同名,你們會知道產生什麼後果。

  有了上面的DECLARE_DYNAMIC(類名)宏以後,咱們在頭文件裏寫上一句:

DECLARE_DYNAMIC(XXX)

  宏展開後就有了咱們想要的:

public:
static CRuntimeClass classXXX //XXX爲類名
virtual CRuntime* GetRuntimeClass() const;

  對於IMPLEMENT_DYNAMIC(類名,基類名),看來也不值得在這裏代換文字了,你們知道它是知道回事,宏展開後爲咱們作了什麼,再深究真是一點意義都沒有!

  有了此鏈表以後,就像有了一張存放各種型的網,咱們能夠垂手可得地RTTI。CObject有一個函數BOOL IsKindOf(const CRuntimeClass* pClass) const;,被它如下全部派生員繼承。

  此函數實現以下:

BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
{
CRuntimeClass* pClassThis=GetRuntimeClass();//得到本身的CRuntimeClass對象指針。
while(pClassThis!=NULL)
{
if(pClassThis==pClass) return TRUE;
pClassThis=pClassThis->m_pBaseClass;//這句最關鍵,指向本身基類,再回頭比較,一直到盡頭m_pBaseClass爲NULL結束。
}
return FALSE;
}

  說到這裏,運行時類型識別(RTTI)算是完成了。寫這篇文章的時候,我一直重感冒。我曾一度在想,究竟寫這東西是爲了什麼。由於若是我把這些時間用在別的地方(甚至幫別人打打字),應該有數百元的報酬。

  是什麼讓「嗜財如命」的我繼續寫下去?我想,無非是想交幾個計算機的朋友而已。計算機是你們公認高科技的東西,但學習它的朋友大多隻能默默無聞,外界的朋友也不知道怎麼去認識你。程序員更不是「潮流」的東西,更加得不到別人的承認。

  有一件我的認爲很典型的事情,有一天,我跟一個朋友到一個單位裏面。裏面有一個女打字員。朋友看着她熟練的指法,心悅誠服地說:「她的電腦水平比你的又高了一個很高的層次!」,那個女的打字高手亦自豪地說:「我靠電腦爲生,電腦水平確定比你(指筆者)的好一點!換着是你,若是以電腦爲生,我也不敢說好過你!」。雖然我想聲明我是計算機專業的,但我知道沒有理解,因此我只得客氣地點頭。

  選擇電腦「潮流」的東西實際是選擇了平凡,而選擇作程序員就是選擇了孤獨!幸虧我不是一個專門的程序員,但即便如此,我願意作大家的朋友,由於我愛大家!

http://www.yesky.com/SoftChannel/72342371928702976/20050228/1915827.shtml

///////////////////////////////
首先,很很差意思的說明,我還正在看C++ language programming,但尚未看到關於RTTI的章節。另外,我也不多使用C++ RTTI的特性。因此對RTTI的理解僅限於本身的摸索和思考。若是不正確,請你們指正。

      RTTI特性是C++語言加入較晚的特性之一。和其餘語言(好比JAVA)相比,C++的RTTI能力算是很是差的。這與C++的設計要求應該有重要的關係:性能。沒錯,性能的因素使得C++的不少地方不能稱的上完美,可是也正由於如此,在高級通用語言裏面,只有C能和C++的性能能夠相提並論。

1:typeid的研究

     在C++中,彷佛與RTTI相關的只有一個東西,就是dynamic_cast,原本我認爲typeid是RTTI的一部分,可是個人實驗代表,並不是如此。typeid的操做是在編譯時期就已經決定的了。下面的代碼能夠證實:

#include 
#include

class A
{
};

class B:public A
{
};

int main()
{
   A *pa;
   B b,*pb;
   pb = &b;
   pa = pb;
   std::cout<<"Name1:"
        << (typeid(pa).name())
        <<"/tName2:"
        <<(typeid(pb).name())
        <<:endl;< p=""></:ENDL;<>

   std::cout<<"pa == pb:"<< (typeid(pa) == typeid(pb))<<:endl;
   return 0;
}


typeid根本不能判別pa其實是一個B*。換句話說,typeid是以字面意思去解釋類型,不要期望它能認出一個void*其實是int*(這個連人也作不到:P)。實際上實用價值不大。

固然,在某些特殊地方,也是可以有些效用的,好比模板。

template 
void test(T t)
{
 if(typeid(t) == typeid(char *))
 {
   // 對char *特殊處理
 }
 //...
}


若是編譯器優化的好的話,並不會產生廢代碼,由於typeid編譯時期就能夠決定了。

 

2:dynamic_cast

    抱歉如今纔講到正題,我對dynamic_cast第一印象就是,它到底是怎麼實現的呢?通過一些思考,我認爲最簡單的方案就是將信息保存在vtable裏,它會佔用一個vtalbe表的項目。實驗和書籍也證實了這一點。可是就會有一個問題,沒有vtable的類怎麼辦?內建類型怎麼辦?其實,沒有vtable的類,它不須要多態,它根本就不須要RTTI,內建類型也同樣。這就是說,dynamic_cast只支持有虛函數的類。並且, dynamic_cast不能進行non_base_class *到 class T*的轉換,好比void * --> class T *,由於它沒法去正確得到vtable。

     這樣,dynamic_cast的意義和使用方法就很清楚了,它是爲了支持多態而存在的。它用於實現從基類到派生類的安全轉換。同時它也在絕大多數狀況下避免了使用static_cast--不安全的類型轉換。

3:結論

    C++ 的RTTI機制雖然簡單,或者說簡陋,可是它使得靜態類型轉換變得無用了。這也是C++的一個不可缺乏的機制。在將來,若是C++可以提供可選的更強的RTTI機制,就像JAVA裏的那樣,這種語言能夠變得更增強大。固然,到時如何提供不損失性能的 RTTI機制,更是一個值得深刻研究的話題了。

第三章

摘要:

  RTTI(Run-Time Type Identification)是面向對象程序設計中一種重要的技術。現行的C++標準對RTTI已經有了明確的支持。不過在某些狀況下出於特殊的開發須要,咱們須要本身編碼來實現。本文介紹了一些關於RTTI的基礎知識及其原理和實現。

RTTI需求:

  和不少其餘語言同樣,C++是一種靜態類型語言。其數據類型是在編譯期就肯定的,不能在運行時更改。然而因爲面向對象程序設計中多態性的要求,C++中的指針或引用(Reference)自己的類型,可能與它實際表明(指向或引用)的類型並不一致。有時咱們須要將一個多態指針轉換爲其實際指向對象的類型,就須要知道運行時的類型信息,這就產生了運行時類型識別的要求。

  C++對RTTI的支持:

  C++提供了兩個關鍵字typeid和dynamic_cast和一個type_info類來支持RTTI:

  dynamic_cast操做符:它容許在運行時刻進行類型轉換,從而使程序可以在一個類層次結構安全地轉換類型。dynamic_cast提供了兩種轉換方式,把基類指針轉換成派生類指針,或者把指向基類的左值轉換成派生類的引用。見下例講述:

void company::payroll(employee *pe) {
//對指針轉換失敗,dynamic_cast返回NULL
if(programmer *pm=dynamic_cast(pe)){
pm->bonus(); 
}
}
void company::payroll(employee &re) {
try{
//對引用轉換失敗的話,則會以拋出異常來報告錯誤
programmer &rm=dynamic_cast(re);
pm->bonus();
}
catch(std::bad_cast){

}
}


  這裏bonus是programmer的成員函數,基類employee不具有這個特性。因此咱們必須使用安全的由基類到派生類類型轉換,識別出programmer指針。

  typeid操做符:它指出指針或引用指向的對象的實際派生類型。

  例如:

employee* pe=new manager;
typeid(*pe)==typeid(manager) //true


  typeid能夠用於做用於各類類型名,對象和內置基本數據類型的實例、指針或者引用,看成用於指針和引用將返回它實際指向對象的類型信息。typeid的返回是type_info類型。

  type_info類:這個類的確切定義是與編譯器實現相關的,下面是《C++ Primer》中給出的定義(參考資料[2]中談到編譯器必須提供的最小信息量):

class type_info {
private:
type_info(const type_info&);
type_info& operator=( const type_info& );
public:
virtual ~type_info();
int operator==( const type_info& ) const;
int operator!=( const type_info& ) const;
const char* name() const;
};

實現目標:

  實現的方案

  方案一:利用多態來取得指針或應用的實際類型信息

  這是一個最簡單的方法,也是做者目前所採用的辦法。

  實現:

enum ClassType{
UObjectClass,
URectViewClass,
UDialogClass,
……
};
class UObject{
virtual char* GetClassName() const {
return "UObject";
};
virtual ClassType TypeOfClass(){
return UObjectClass;
};
};
class UDialog{
virtual char* GetClassName() const {
return "UDialog";
};
virtual ClassType TypeOfClass(){
return UDialogClass;
};
};


  示例:

UObject po=new UObject;
UObject pr=new URectView;
UObject pd=new UDialog;
cout << "po is a " << po->GetClassName() << endl;
cout << "pr is a " << pr->GetClassName() << endl;
cout << "pd is a " << pd->GetClassName() << endl;
cout<TypeOfClass()==UObjectClass< cout<TypeOfClass()==URectViewClass<
cout<TypeOfClass()==UDialogClass<<ENDL;
cout<TypeOfClass()==UObjectClass<<ENDL;
cout<TypeOfClass()==UDialogClass<<ENDL;< td>


  輸出:

po is a UObjectClass
pr is a URectViewClass
pd is a UDialogClass
true
true
true
false
false


  這種實現方法也就是在基類中提供一個多態的方法,這個方法返回一個類型信息。這樣咱們可以知道一個指針所指向對象的具體類型,能夠知足一些簡單的要求。

  可是很顯然,這樣的方法只實現了typeid的部分功能,還存在不少缺點:

  一、 用戶每增長一個類必須覆蓋GetClassName和TypeOfClass兩個方法,若是忘了,會致使程序錯誤。

  二、 這裏的類名和類標識信息不足以實現dynamic_cast的功能,從這個意義上而言此方案根本不能稱爲RTTI。

  三、 用戶必須手工維護每一個類的類名與標識,這限制了以庫的方式提供給用戶的可能。

  四、 用戶必須手工添加GetClassName和TypeOfClass兩個方法,使用並不方便。

  其中上面的部分問題咱們能夠採用C/C++中的宏技巧(Macro Magic)來解決,這個能夠在咱們的最終解決方案的代碼中看到。下面採用方案二中將予以解決上述問題。

方案二:以一個類型表來存儲類型信息

  這種方法考慮使用一個類結構,除了保留原有的整型類ID,類名字符串外,增長了一個指向基類TypeInfo成員的指針。

struct TypeInfo
{
char* className;
int type_id;
TypeInfo* pBaseClass;
operator== (const TypeInfo& info){
return this==&info;
}
operator!= (const TypeInfo& info){
return this!=&info;
}
};


  從這裏能夠看到,以這種方式實現的RTTI不支持多重繼承。所幸多重繼承在程序設計中並不是必須,並且也不推薦。下面的代碼中,我將爲DP9900軟件項目組中類層次結構中的幾個類添加RTTI功能。DP9900項目中,絕大部分的類都以單繼承方式從UObject這個根類直接或間接繼承而來。這樣咱們就能夠從UObject開始,加入咱們RTTI支持所須要的數據和方法。

class UObject
{
public:
bool IsKindOf(TypeInfo& cls); //判別某個對象是否屬於某一個類
public:
virtual int GetTypeID(){return rttiTypeInfo.type_id;}
virtual char* GetTypeName(){return rttiTypeInfo.className;}
virtual TypeInfo& GetTypeInfo(){return rttiTypeInfo;}
static TypeInfo& GetTypeInfoClass(){return rttiTypeInfo;}
private:
static TypeInfo rttiTypeInfo; 
};
//依次爲className、type_id、pBaseClass賦值
TypeInfo UObject::rttiTypeInfo={"UObject",0,NULL};


  考慮從UObject將這個TypeInfo類做爲每個新增類的靜態成員,這樣一個類的全部對象將共享TypeInfo的惟一實例。咱們但願可以在程序運行以前就爲type_id,className作好初始化,並讓pBaseClass指向基類的這個TypeInfo。

  每一個類的TypeInfo成員約定使用rttiTypeInfo的命名,爲了不命名衝突,咱們將其做爲private成員。有了基類的支持並不夠,當用戶須要RTTI支持,還須要本身來作一些事情:

  一、 派生類須要從UObject繼承。

  二、 添加rttiTypeInfo變量。

  三、 在類外正確初始化rttiTypeInfo靜態成員。

  四、 覆蓋GetTypeID、GetTypeName、GetTypeInfo、GetTypeInfoClass四個成員函數。

  以下所示:

class UView:public UObject
{
public:
virtual int GetTypeID(){return rttiTypeInfo.type_id;} 
virtual char* GetTypeName(){return rttiTypeInfo.className;} 
virtual TypeInfo& GetTypeInfo(){return rttiTypeInfo;} 
static TypeInfo& GetTypeInfoClass(){return rttiTypeInfo;} 
private: 
static TypeInfo rttiTypeInfo; 
};


  有了前三步,這樣咱們就能夠獲得一個不算太複雜的鏈表――這是一棵類型信息構成的"樹",與數據結構中的樹的惟一差異就是其指針方向相反。

  這樣,從任何一個UObject的子類,順着pBaseClass往上找,總能遍歷它的全部父類,最終到達UObject。

  在這個鏈表的基礎上,要判別某個對象是否屬於某一個類就很簡單。下面給出UObject::IsKindOf()的實現。

bool UObject::IsKindOf(TypeInfo& cls)
{
TypeInfo* p=&(this->GetTypeInfo());
while(p!=NULL){
if(p->type_id==cls.type_id)
return true;
p=p->pBaseClass;
}
return false;
}


  有了IsKindOf的支持,dynamic_cast的功能也就能夠用一個簡單的safe_cast來實現:

template 
inline T* safe_cast(UObject* ptr,TypeInfo& cls)
{
return (ptr->IsKindOf(cls)?(T*)ptr:NULL);
}


  至此,咱們已經可以從功能上完成前面的目標了,不過用戶要使用這個類庫的RTTI功能還很麻煩,要敲入一大堆對他們毫無心義的函數代碼,要在初始化rttiTypeInfo靜態成員時手工設置類ID與類名。其實這些麻煩徹底沒必要交給咱們的用戶,適當採用一些宏技巧(Macro Magic),就可讓C++的預處理器來替咱們寫不少枯燥的代碼。關於宏不是本文的重點,你能夠從最終代碼清單看到它們。下面再談談關於類ID的問題。

  類ID

  爲了使不一樣類型的對象可區分,用一個給每一個TypeInfo對象一個類ID來做爲比較的依據是必要的。
其實對於咱們這裏的需求和實現方法而言,其實類ID並非必須的。每個支持RTTI的類都包含了一個靜態TypeInfo對象,這個對象的地址就是在進程中全局惟一。但考慮到其餘一些技術如:動態對象建立、對象序列化等,它們可能會要求RTTI給出一個靜態不變的ID。在本文的實現中,對此做了有益的嘗試。

  首先聲明一個用來產生遞增類ID的全局變量。再聲明以下一個結構,沒有數據成員,只有一個構造函數用於初始化TypeInfo的類ID:

extern int TypeInfoOrder=0;
struct InitTypeInfo
{
InitTypeInfo(TypeInfo* info)
{
info->type_id=TypeInfoOrder++;
}
};


  爲UObject添加一個private的靜態成員及其初始化:

class UObject
{
//……
private:
static InitTypeInfo initClassInfo;
};
InitTypeInfo UObject::initClassInfo(&(UObject::rttiTypeInfo));


  而且對每個從UObject派生的子類也進行一樣的添加。這樣您將看到,在C++主函數執行前,啓動代碼將替咱們調用每個類的initClassInfo成員的構造函數InitTypeInfo::InitTypeInfo(TypeInfo* info),而正是這個函數替咱們產生並設置了類ID。InitTypeInfo的構造函數還能夠替咱們作其餘一些有用的初始化工做,好比將全部的TypeInfo信息登陸到一個表格裏,讓咱們能夠很方便的遍歷它。

  但實踐與查閱資料讓咱們發現,因爲C++中對靜態成員初始化的順序沒有明確的規定,因此這樣的方式產生出來的類ID並不是徹底靜態,換一個編譯器編譯執行產生的結果可能徹底不一樣。

  還有一個能夠考慮的方案是採用某種無衝突HASH算法,將類名轉換成爲一個惟一整數。使用標準CRC32算法從類型名計算出一個整數做爲類ID也許是個不錯的想法[3]。

  程序清單

// URtti.h 
#ifndef __URTTI_H__
#define __URTTI_H__

class UObject;

struct TypeInfo
{
char* className;
int type_id;
TypeInfo* pBaseClass;
operator== (const TypeInfo& info){
return this==&info;
}
operator!= (const TypeInfo& info){
return this!=&info;
}
};

inline std::ostream& operator<< (std::ostream& os,TypeInfo& info)
{
return (os<< "[" << &info << "]" << "/t"
<< info.type_id << ":"
<< info.className << ":"
<< info.pBaseClass << std::endl);
}

extern int TypeInfoOrder;

struct InitTypeInfo
{
InitTypeInfo(/*TypeInfo* base,*/TypeInfo* info)
{
info->type_id=TypeInfoOrder++;
}
};

#define TYPEINFO_OF_CLASS(class_name) (class_name::GetTypeInfoClass())
#define TYPEINFO_OF_OBJ(obj_name) (obj_name.GetTypeInfo())
#define TYPEINFO_OF_PTR(ptr_name) (ptr_name->GetTypeInfo())

#define DECLARE_TYPEINFO(class_name) /
public: /
virtual int GetTypeID(){return TYPEINFO_MEMBER(class_name).type_id;} /
virtual char* GetTypeName(){return TYPEINFO_MEMBER(class_name).className;} /
virtual TypeInfo& GetTypeInfo(){return TYPEINFO_MEMBER(class_name);} /
static TypeInfo& GetTypeInfoClass(){return TYPEINFO_MEMBER(class_name);} /
private: /
static TypeInfo TYPEINFO_MEMBER(class_name); /
static InitTypeInfo initClassInfo; /

#define IMPLEMENT_TYPEINFO(class_name,base_name) /
TypeInfo class_name::TYPEINFO_MEMBER(class_name)= /
{#class_name,0,&(base_name::GetTypeInfoClass())}; /
InitTypeInfo class_name::initClassInfo(&(class_name::TYPEINFO_MEMBER(class_name)));

#define DYNAMIC_CAST(object_ptr,class_name) /
safe_cast(object_ptr,TYPEINFO_OF_CLASS(class_name))

#define TYPEINFO_MEMBER(class_name) rttiTypeInfo

class UObject
{
public:
bool IsKindOf(TypeInfo& cls);
public:
virtual int GetTypeID(){return TYPEINFO_MEMBER(UObject).type_id;}
virtual char* GetTypeName(){return TYPEINFO_MEMBER(UObject).className;}
virtual TypeInfo& GetTypeInfo(){return TYPEINFO_MEMBER(UObject);}
static TypeInfo& GetTypeInfoClass(){return TYPEINFO_MEMBER(UObject);}
private:
static TypeInfo TYPEINFO_MEMBER(UObject);
static InitTypeInfo initClassInfo;
};

template 
inline T* safe_cast(UObject* ptr,TypeInfo& cls)
{
return (ptr->IsKindOf(cls)?(T*)ptr:NULL);
}
#endif
// URtti.cpp 
#include "urtti.h"

extern int TypeInfoOrder=0;

TypeInfo UObject::TYPEINFO_MEMBER(UObject)={"UObject",0,NULL};
InitTypeInfo UObject::initClassInfo(&(UObject::TYPEINFO_MEMBER(UObject)));

bool UObject::IsKindOf(TypeInfo& cls)
{
TypeInfo* p=&(this->GetTypeInfo());
while(p!=NULL){
if(p->type_id==cls.type_id)
return true;
p=p->pBaseClass;
}
return false;
}
// mail.cpp 
#include 
#include "urtti.h"
using namespace std;

class UView:public UObject
{
DECLARE_TYPEINFO(UView)
};
IMPLEMENT_TYPEINFO(UView,UObject)

class UGraph:public UObject
{
DECLARE_TYPEINFO(UGraph)
};
IMPLEMENT_TYPEINFO(UGraph,UObject)

void main()
{
UObject* po=new UObject;
UView* pv=new UView;
UObject* pg=new UGraph;
if(DYNAMIC_CAST(po,UView)) 
cout << "po => UView succeed" << std::endl;
else
cout << "po => UView failed" << std::endl;
if(DYNAMIC_CAST(pv,UView))
cout << "pv => UView succeed" << std::endl;
else
cout << "pv => UView failed" << std::endl;
if(DYNAMIC_CAST(po,UGraph)) 
cout << "po => UGraph succeed" << std::endl;
else
cout << "po => UGraph failed" << std::endl;
if(DYNAMIC_CAST(pg,UGraph))
cout << "pg => UGraph succeed" << std::endl;
else
cout << "pg => UGraph failed" << std::endl;
}


  實現結果

  本文實現了以下幾個宏來支持RTTI,它們的使用方法均可以在上面的代碼中找到:
  

宏函數

功能及參數說明

DECLARE_TYPEINFO(class_name)

爲類添加RTTI功能放在類聲明的起始位置

IMPLEMENT_TYPEINFO(class_name,base)

同上,放在類定義任何位置

TYPEINFO_OF_CLASS(class_name)

至關於typeid(類名)

TYPEINFO_OF_OBJ(obj_name)

至關於typeid(對象)

TYPEINFO_OF_PTR(ptr_name)

至關於typeid(指針)

DYNAMIC_CAST(object_ptr,class_name)

至關於dynamic_castobject_ptr

性能測試

  測試代碼:

  這裏使用相同次數的DYNAMIC_CAST和dynamic_cast進行對比測試,在VC6.0下編譯運行,使用默認的Release編譯配置選項。爲了不編譯器優化致使的不公平測試結果,我在循環中加入了無心義的計數操做。

void main()
{
UObject* po=new UObject;
UView* pv=new UView;
UObject* pg=new UGraph;
int a,b,c,d;
a=b=c=d=0;
const int times=30000000;
cerr << "時間測試輸出:" << endl;
cerr << "start my DYNAMIC_CAST at: " << time(NULL) << endl;
for(int i=0;i<TIMES;I++){
if(DYNAMIC_CAST(po,UView)) a++; else a--;
if(DYNAMIC_CAST(pv,UView)) b++; else b--;
if(DYNAMIC_CAST(po,UGraph)) c++; else c--;
if(DYNAMIC_CAST(pg,UGraph)) d++; else d--;
}
cerr << "end my DYNAMIC_CAST at: " << time(NULL) << endl;
cerr << "start c++ dynamic_cast at: " << time(NULL) << endl;
for(i=0;i<TIMES;I++){
if(dynamic_cast(po)) a++; else a--;
if(dynamic_cast(pv)) b++; else b--;
if(dynamic_cast(po)) c++; else c--;
if(dynamic_cast(pg)) d++; else d--;
}
cerr << "end c++ dynamic_cast at: " << time(NULL) << endl;
cerr << a << b << c << d << endl;
}


  運行結果:

start my DYNAMIC_CAST at: 1021512140
end my DYNAMIC_CAST at: 1021512145
start c++ dynamic_cast at: 1021512145
end c++ dynamic_cast at: 1021512160


  這是上述條件下的測試輸出,咱們能夠看到,本文實現的這個精簡RTTI方案運行DYNAMIC_CAST的時間開銷只有dynamic_cast的1/3。爲了獲得更全面的數據,還進行了DEBUG編譯配置選項下的測試。

  輸出:

start my DYNAMIC_CAST at: 1021512041
end my DYNAMIC_CAST at: 1021512044
start c++ dynamic_cast at: 1021512044
end c++ dynamic_cast at: 1021512059


  這種狀況下DYNAMIC_CAST運行速度要比dynamic_cast慢一倍左右。若是在Release編譯配置選項下將UObject::IsKindOf方法改爲以下inline函數,咱們將獲得更讓人興奮的結果(DYNAMIC_CAST運行時間只有dynamic_cast的1/5)。

inline bool UObject::IsKindOf(TypeInfo& cls)
{
for(TypeInfo* p=&(this->GetTypeInfo());p!=NULL;p=p->pBaseClass)
if(p==&cls) return true;
return false;
}


  輸出:

start my DYNAMIC_CAST at: 1021512041
end my DYNAMIC_CAST at: 1021512044
start c++ dynamic_cast at: 1021512044
end c++ dynamic_cast at: 1021512059


  結論:

  由本文的實踐能夠得出結論,本身動手編碼實現RTTI是簡單可行的。這樣的實現能夠在編譯器優秀的代碼優化中表現出比dynamic_cast更好的性能,並且沒有帶來過多的存儲開銷。本文的RTTI以性能爲主要設計目標,在實現上必定程度上受到了MFC的影響。適於嵌入式環境。

第三章

連接指示符extern C
若是程序員但願調用其餘程序設計語言尤爲是C 寫的函數,那麼調用函數時必須
告訴編譯器使用不一樣的要求,例如當這樣的函數被調用時,函數名或參數排列的順序可能
不一樣,不管是C++函數調用它仍是用其餘語言寫的函數調用它
程序員用連接指示符linkage directive 告訴編譯器該函數是用其餘的程序設計語言
編寫的,連接指示符有兩種形式:既能夠是單一語句single statement 形式。也能夠是復
合語句compound statement 形式
// 單一語句形式的連接指示符
extern "C" void exit(int);
// 複合語句形式的連接指示符
extern "C" {
int printf( const char* ... );
int scanf( const char* ... );
}
// 複合語句形式的連接指示符
extern "C" {
#include <cmath>
}
連接指示符的第一種形式由關鍵字extern 後跟一個字符串常量以及一個普通的函數
聲明構成,雖然函數是用另一種語言編寫的,但調用它仍然須要類型檢查,例如編譯器
會檢查傳遞給函數exit()的實參的類型是不是int ,或者可以隱式地轉換成int 型
多個函數聲明能夠用花括號包含在連接指示符複合語句中,這是連接指示符的第二種形
式,花招號被用做分割符表示連接指示符應用在哪些聲明上,在其餘意義上該花括號被忽
略,因此在花括號中聲明的函數名對外是可見的就好像函數是在複合語句外聲明的同樣
例如在前面的例子中複合語句extern "C"表示函數printf()和scanf()是在C 語言中寫的,
函數所以這個聲明的意義就如同printf()和scanf()是在extern "C"複合語句外面聲明的
同樣,
當複合語句連接指示符的括號中含有#include 時在頭文件中的函數聲明都被假定是用
連接指示符的程序設計語言所寫的在前面的例子中在頭文件<cmath>中聲明的函數都是C
函數
連接指示符不能出如今函數體中下列代碼段將會致使編譯錯誤
int main()
{
// 錯誤: 連接指示符不能出如今函數內
extern "C" double sqrt( double );
305 第七章函數
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
若是把連接指示符移到函數體外程序編譯將無錯誤
extern "C" double sqrt( double );
int main()
{
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
可是把連接指示符放在頭文件中更合適,在那裏函數聲明描述了函數的接口所屬
若是咱們但願C++函數可以爲C 程序所用又該怎麼辦呢?咱們也可使用extern "C"
連接指示符來使C++函數爲C 程序可用例如:
// 函數calc() 能夠被C 程序調用
extern "C" double calc( double dparm ) { /* ... */ }
若是一個函數在同一文件中不僅被聲明一次,則連接指示符能夠出如今每一個聲明中,它
也能夠只出如今函數的第一次聲明中在這種狀況下第二個及之後的聲明都接受第一個聲
明中連接指示符指定的連接規則例如
// ---- myMath.h ----
extern "C" double calc( double );
// ---- myMath.C ----
// 在Math.h 中的calc() 的聲明
#include "myMath.h"
// 定義了extern "C" calc() 函數
// calc() 能夠從C 程序中被調用
double calc( double dparm ) { // ...
在本節中咱們只看到爲C 語言提供的連接指示extern "C", extern "C"是唯一被
保證由全部C++實現都支持的每一個編譯器實現均可覺得其環境下經常使用的語言提供其餘連接
指示,例如:extern "Ada"能夠用來聲明是用Ada 語言寫的函數,extern "FORTRAN"用來
聲明是用FORTRAN 語言寫的函數等等,由於其餘的連接指示隨着具體實現的不一樣而不一樣
因此建議讀者查看編譯器的用戶指南以得到其餘連接指示符的

第四章

Run-time type information (RTTI) is a mechanism that allows the type of an object to be determined during program execution. 
RTTI was added to the C++ language because many vendors of class libraries were implementing this functionality themselves. 
This caused incompatibilities between libraries. 
Thus, it became obvious that support for run-time type information was needed at the language level.

For the sake of clarity, this discussion of RTTI is almost completely restricted to pointers. However, the concepts discussed also apply to references.

There are three main C++ language elements to run-time type information:

Used for conversion of polymorphic types.

Used for identifying the exact type of an object.

Used to hold the type information returned by the typeid operator.

Visual C++ Compiler Options

/GR (Enable Run-Time Type Information)

This option (/GR) adds code to check object types at run time.
When this option is specified, the compiler defines the _CPPRTTI preprocessor macro. The option is cleared (/GR–) by default.

To set this compiler option in the Visual Studio development environment 
   1. Open the project's Property Pages dialog box. For details, 
         see Setting Visual C++  Project Properties. 
   2. Click the C/C++ folder. 
   3. Click the Language property page. 
   4. Modify the Enable Run-Time Type Info property. 

典型的RTTI是經過在VTable中放一個額外的指針來實現的。這個指針指向一個描述該特定類型的
typeinfo結構(每一個新類只產生一個typeinfo的實例),因此typeid()表達式的做用實際上很簡單。
VPtr用來取typeinfo的指針,而後產生一個結果typeinfo結構的一個引用,而後調用庫中的一個例程判
斷源typeinfo是否與目標typeinfo相同或者是目標typeinfo的派生類。

MFC的RTTI主要是經過CRuntimeClass實現的。咱們的類只需從CObject派生,並分別在頭文件和實
現文件中聲明DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC或
DECLARE_DYNCREATE/IMPLEMENT_DYNCREATE或
DECLARE_SERIAL/IMPLEMENT_SERIAL 宏就可擁有RTTI。

CRuntimeClass is a structure and therefore does not have a base class.

CRuntimeClass is a structure you can use to obtain information about an object or its 
base class at run time. The ability to determine the class of an object at run time is 
useful when extra type checking of function arguments is needed, or when you must 
write special-purpose code based on the class of an object.

Each class derived from CObject is associated with a CRuntimeClass structure that you can use to obtain information about an object or its base class at run time. Run-time 
class information is not supported directly by the C++ language.

CRuntimeClass provides information on the related C++ object, such as a pointer to 
the CRuntimeClass of the base class and the ASCII class name of the related class. 
This structure also implements various functions that can be used to dynamically create 
objects, specifying the type of object by using a familiar name, and determining if the 
related class is derived from a specific class.

struct CRuntimeClass {
 LPCSTR m_lpszClassName;
 int m_nObjectSize;
 UINT m_wSchema CObject* (PASCAL* m_pfnCreateObject)( );
 CRuntimeClass* (PASCAL* m_pfnGetBaseClass)( );
 CRuntimeClass* m_pBaseClass;
 CObject* CreateObject( );
 BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;
 void Store(CArchive& ar) const;
 static CRuntimeClass* PASCAL Load( Carchive& ar, UINT* pwSchemaNum);
 CRuntimeClass* m_pNextClass;
 };

類比:   1. CObject::GetRuntimeClass() <==> typeid(object)   2. RUNTIME_CLASS(classname) <==> typeid(classname)   3. CObject::IsKindOf(RUNTIME_CLASS(classname))          <==>       typeid(object) == typeid (classname)   4. CRuntimeClass::IsDeriveFrom(RUNTIME_CLALL(classname))         <==>       dynamic_cast<classname*>(ptr) != NULL   在編寫MFC程序的時候,最好不要用c++ RTTI以避免增長開銷。由於他們的實現各自獨立並且MFC的CRuntime實現的功能是RTTI的超集,由於CRuntime還支持動態建立,於是也支持序列化。

相關文章
相關標籤/搜索