typeid運運算元
執行時型別識別(rtti)即是程式執行過程中知道某個物件屬於某個類,我們平時用c++程式設計接觸的rtti一般是編譯器的rtti,即是在新版本的vc++編譯器裡面選用「使能rtti」,然後載入typeinfo.h檔案,就可以使用乙個叫typeid()的運運算元,它的地位與在c++程式設計中的sizeof()運運算元類似的地方(包含乙個標頭檔案,然後就有乙個熟悉好用的函式)。typdid()關鍵的地方是可以接受兩個型別的引數:乙個是類名稱,乙個是物件指標。所以我們判別乙個物件是否屬於某個類就可以象下面那樣:
c++** if
(typeid
(classname)==
typeid
(*objectname))
像上面所說的那樣,乙個typeid()運運算元就可以輕鬆地識別乙個物件是否屬於某乙個類,但mfc並不是用typeid()的運運算元來進行動態型別識別,而是用一大堆令人費解的巨集。很多學員在這裡很疑惑,好象mfc在大部分地方都是故作神秘。使們大家程式設計時很迷惘,只知道在這裡加入一組巨集,又在那兒加入乙個對映,而不知道我們為什麼要加入這些東東。
其實,早期的mfc並沒有typeid()運運算元,所以只能沿用乙個老辦法。我們甚至可以想象一下,如果mfc早期就有template(模板)的概念,可能更容易解決rtti問題。
所以,我們要回到「古老」的年代,想象一下,要完成rtti要做些什麼事情。就好像我們在乙個新型(新型到我們還不認識)電器公司裡面,我們要識別哪個是電飯鍋,哪個是電磁爐等等,我們要檢視登記的各電器一系列的資訊,我們才可以比較、鑑別,那個東西是什麼!
cruntimeclass鍊錶的設計
要登記一系列的訊息並不是一件簡單的事情,大家可能首先想到用陣列登記物件。但如果用陣列,我們要定義多大的陣列才好呢,大了浪費空間,小了更加不行。所以我們要用另一種資料結構——鍊錶。因為鍊錶理論上可大可小,可以無限擴充套件。
鍊錶是一種常用的資料結構,簡單地說,它是在乙個物件裡面儲存了指向下乙個同型別物件的指標。我們大體可以這樣設計我們的類:
c++**
struct
cruntimeclass ;
鍊錶還應該有乙個表頭和乙個表尾,這樣我們在查煉表中各物件元素的資訊的時候才知道從**查起,到哪兒結束。我們還要註明本身是由哪能個類派生。所以我們的鍊錶類要這樣設計:
c++**
struct
cruntimeclass ;
有了cruntimeclass結構後,我們就可以定義鍊錶了:
c++**
static
cruntimeclass classcobject=;
//這裡定義了乙個cruntimeclass物件,因為classcobject無基類,所以m_pbaseclass為null。因為目前只有乙個元素(即目前沒有下一元素),所以m_pnextclass為null(表尾)。
至於pfirstclass(表頭),大家可能有點想不通,它到什麼地方去了。因為我們這裡並不想把classcobject作為煉表表頭,我們還要在前面插入很多的cruntimeclass物件,並且因為pfirstclass為static指標,即是說它不是屬於某個物件,所以我們在用它之前要先初始化:cruntimeclass* cruntimeclass::pfirstclass=null;。
現在我們可以在前面插入乙個cruntimeclass物件,插入之前我得重要申明一下:如果單純為了執行時型別識別,我們未必用到m_pnextclass指標(更多是在執行時建立時用),我們關心的是類本身和它的基類。這樣,查詢乙個物件是否屬於乙個類時,主要關心的是類本身及它的基類。
c++**
cruntimeclass classccmdtarget=;
cruntimeclass classcwnd=;
cruntimeclass classcview=;
好了,上面只是僅僅為乙個指標m_pbaseclass賦值(mfc中真正cruntimeclass有多個成員變數和方法),就連線成了鍊錶。假設我們現在已全部構造完成自己需要的cruntimeclass物件,那麼,這時候應該定義表頭。即要用pfirstclass指標指向我們最後構造的cruntimeclass物件——classcview。
c++**
cruntimeclass::pfirstclass=&classcview;
現在鍊錶有了,表頭表尾都完善了,問題又出現了,我們應該怎樣訪問每乙個cruntimeclass物件?要判斷乙個物件屬於某類,我們要從表頭開始,一直向表尾查詢到表尾,然後才能比較得出結果嗎。肯定不是這樣!
類中構造cruntimeclass物件
大家可以這樣想一下,我們構造這個鍊錶的目的,就是構造完之後,能夠按主觀地拿乙個cruntimeclass物件和鍊錶中的元素作比較,看看其中乙個物件是否屬於你指定的類。這樣,我們需要有乙個函式,乙個能返回自身型別名的函式getruntimeclass()。
上面簡單地說了一下鍊錶的過程,但單純有這個鍊錶是沒有任何意義。回到mfc中來,我們要實現的是在每個需要有rtti能力的類中構造乙個cruntimeclass物件,比較乙個類是否屬於某個cruntimeclass物件的時候,實際上只是比較cruntimeclass物件。
如何在各個類之中插入cruntimeclass物件,並且指定cruntimeclass物件的內容及cruntimeclass物件的鏈結,這裡起碼有十行的**才能完成。在每個需要有rtti能力的類設計中都要重複那十多行**是一件乏味的事情,也容易出錯,所以mfc用了兩個巨集代替這些工作,即declare_dynamic(類名)和implement_dynamic(類名,基類名)。從這兩個巨集我們可以看出在mfc名類中的cruntimeclass物件構造連線只有類名及基類名的不同!
到此,可能會有朋友問:為什麼要用兩個巨集,用乙個巨集不可以代換cruntimeclass物件構造連線嗎?個人認為肯定可以,因為巨集只是文字代換的遊戲而已。但我們在程式設計之中,標頭檔案與原始檔是分開的,我們要在標頭檔案頭宣告變數及方法,在原始檔里實具體實現。即是說我們要在標頭檔案中宣告:
c++**
public
: static
cruntimeclass class***
//***為類名
virtual
cruntime* getruntimeclass()
const;
然後在原始檔裡實現:
c++**
cruntimeclass* ***::class***=;
cruntime* getruntimeclass()
const
; //這裡不能直接返回&class***,因為static變數是類擁有而不是物件擁有。
我們一眼可以看出mfc中的declare_dynamic(類名)巨集應該這樣定義:
c++**
#define declare_dynamic(class_name) public: static cruntimeclass class##class_name; virtual cruntimeclass* getruntimeclass() const;
其中##為連線符,可以讓我們傳入的類名前面加上class,否則跟原類同名,大家會知道產生什麼後果。
有了上面的declare_dynamic(類名)巨集之後,我們在標頭檔案裡寫上一句
declare_dynamic(***)
巨集展開後就有了我們想要的:
public:
static cruntimeclass class*** //***為類名
virtual cruntime* getruntimeclass() const;
對於implement_dynamic(類名,基類名),看來也不值得在這裡代換文字了,大家知道它是知道回事,巨集展開後為我們做了什麼,再深究真是一點意義都沒有!
有了此鍊錶之後,就像有了一張存放各型別的網,我們可以輕而易舉地rtti。
iskindof函式
cobject有乙個函式bool iskindof(const cruntimeclass* pclass) const;,被它以下所有派生類繼承。
此函式實現如下:
c++**
bool
cobject::iskindof(
const
cruntimeclass* pclass)
const
return
false; }
說到這裡,執行時型別識別(rtti)算是完成了。
來自:
MFC六大核心機制之二 執行時型別識別(RTTI)
上一節講的是mfc六大核心機制之一 mfc程式的初始化,本節繼續講解mfc六大核心機制之二 執行時型別識別 rtti typeid運運算元 執行時型別識別 rtti 即是程式執行過程中知道某個物件屬於某個類,我們平時用c 程式設計接觸的rtti一般是編譯器的rtti,即是在新版本的vc 編譯器裡面選...
MFC的六大核心機制概述
1 mfc程式的初始化工作 在mfc中所有的類都 於乙個基類 cobject。mfc程式初始化過程中,其實就是虛函式的呼叫的過程,分清呼叫執行的到底是哪乙個具體的虛函式,是父類的虛函式,還是基類的虛函式,都是至關重要的。2 rtti執行時型別識別 mfc程式執行過程中需要對類的型別進行動態的判斷。在...
初識MFC 執行時類資訊機制
1 執行時類資訊機制的作用 程式執行過程中,可以獲知物件的相關類的資訊 2 執行時類資訊機制的使用 2.1類必須派生自cobject類 2.2類內必須新增宣告巨集 declare dynamic 2.3類外必須新增實現巨集 implement dynamic cobject iskindof 來判斷...