天天看點

MFC六大核心機制【運作時類型識别(RTTI)】

1、MFC程式的初始化。

2、運作時類型識别(RTTI)。

3、動态建立。

4、永久儲存。

5、消息映射。

6、消息傳遞。

二、運作時類型識别(RTTI)

typeid運算子

 運作時類型識别(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要做些什麼事情。就好像我們在一個新型(新型到我們還不認識)電器公司裡面,我們要識别哪個是電飯鍋,哪個是電磁爐等等,我們要檢視登記的各電器一系列的資訊,我們才可以比較、鑒别,那個東西是什麼!

CRuntimeClass連結清單的設計

要登記一系列的消息并不是一件簡單的事情,大家可能首先想到用數組登記對象。但如果用數組,我們要定義多大的數組才好呢,大了浪費空間,小了更加不行。是以我們要用另一種資料結構——連結清單。因為連結清單理論上可大可小,可以無限擴充。

連結清單是一種常用的資料結構,簡單地說,它是在一個對象裡面儲存了指向下一個同類型對象的指針。我們大體可以這樣設計我們的類:

struct CRuntimeClass   
{   
    //……類的名稱等一切資訊……   
    CRuntimeClass * m_pNextClass;//指向連結清單中下一CRuntimeClass對象的指針   
};  
           

連結清單還應該有一個表頭和一個表尾,這樣我們在查連結清單中各對象元素的資訊的時候才知道從哪裡查起,到哪兒結束。我們還要注明本身是由哪能個類派生。是以我們的連結清單類要這樣設計:

struct CRuntimeClass   
{   
  //……類的名稱等一切資訊……   
  CRuntimeClass * m_pBaseClass;//指向所屬的基類。   
  CRuntimeClass * m_pNextClass;//定義表尾的時候隻要定義此指針為空就可以 了。   
  static CRuntimeClass* pFirstClass;//這裡表頭指針屬于靜态變量,因為我們知道static變量在記憶體中隻初始化一次,就可以為各對象所用!保證了各對象隻有一個表頭。   
};  
           

有了CRuntimeClass結構後,我們就可以定義連結清單了:

//這裡定義了一個CRuntimeClass對象,因為classCObject無基類,是以m_pBaseClass為NULL。
//因為目前隻有一個元素(即目前沒有下一進制素),是以m_pNextClass為NULL(表尾)。  
static CRuntimeClass classCObject={NULL,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對象

大家可以這樣想一下,我們構造這個連結清單的目的,就是構造完之後,能夠按主觀地拿一個CRuntimeClass對象和連結清單中的元素作比較,看看其中一個對象是否屬于你指定的類。這樣,我們需要有一個函數,一個能傳回自身類型名的函數GetRuntimeClass()。

上面簡單地說了一下連結清單的過程,但單純有這個連結清單是沒有任何意義。回到MFC中來,我們要實作的是在每個需要有RTTI能力的類中構造一個CRuntimeClass對象,比較一個類是否屬于某個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。

IsKindOf函數

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)算是完成了。

文章來源:https://blog.csdn.net/ligand/article/details/49848161