如果你感覺前面兩節一帆風順的話,或許從第三節開始,你會感覺理解有一點點困難,或許讀完整節,你只能MFC說有這麼一個東西,但是並不明白這樣設計的好處或如何去使用,別急,讓我們慢慢來學習它。
*RTTI(運行時類型識別)
初看RTTI(Runtime Type Identification),我並不是很明白這個玩意的作用,書中有這樣的一種比方:當你看到一種顏色,想知道它的RGB值,不查表可以嗎?當有你有一種商品,想知道它的型號不差目錄行嗎?算是明白了,這不僅是一個類類型判斷問題,也涉及到如何保存這些類信息的問題,即引入一個“類別型錄”的鏈表,這裏鏈表元素以CRuntimeClass來描述。
struct CRuntimeClass
{
//Attributes
LPCSTR m_lpszClassName; //類名
int m_nObjectSize; //對象大小
UINT m_wSchema; //模式號,如果不要求支持序列化特性,該域爲0xFFFF,否則,不能爲0;
CObject *(PASCAL *m_pfnCreateObject)();
CRuntimeClass *m_pBaseClass;
static CRuntimeClass* pFirstClass;
CRuntimeClass* m_pNextClass;
};
結構如上,我們開始分析:
(1) CObject *(PASCAL *m_pfnCreateObject)(); 定義一個返回CObject* 的函數指針,根據函數指針命名,我們可以知道,它完成的是CreateObject的操作,即一個CRuntimeClass我們就可以產生對應的對象。
(2) CRuntimeClass *m_pBaseClass; 定義一個CRuntimeClass指針指向基類CRuntimeClass,在鏈表中不僅有前後關係,我們也要實現縱向的繼承關係。
(3) static CRuntimeClass *pFirstClass; 定義一個靜態成員變量指向鏈表第一個CRuntimeClass,靜態成員變量在產生對象之前進行初始化,並不依賴對象的構造和析構,並且在多個對象中此值唯一。
(4) CRuntimeClass* m_pNextClass; 指針指向鏈表中下一個元素。
介紹完CRuntimeClass,我們來看看我們最終應該實現的效果:
是的,我們需要一種簡便又快捷的方式實現上述鏈表,那麼我們該怎麼辦?至此,我們需要用宏來實現這些關鍵步驟,讓CRuntimeClass被神不知鬼不覺的塞入我們的類中。當然,別擔心,宏只是簡單的文本替換。我們來看看具體實現步驟:
(1)MFC定義了一個叫DECLARE_DYNAMIC (也就是動態聲明的意思)的宏:
#define DECLARE_DYNAMIC(class_name) \
public: \
static CRuntimeClass class##class_name; \
virtual CRuntimeClass *GetRuntimeClass() const;
比如,我們在CWnd中加入宏定義如下:(此處##是講兩個字符串連接到一起)
class CWnd:public CCmdTarget
{
DECLARE_DYNAMIC(CWnd)
}
實際上我們加入的是:
class CWnd:public CCmdTarget
{
public:
static CRuntimeClass classCWnd;
virtual CRuntimeClass* GetRuntimeClass() const;
}
我們來分析以上結構:(1) static CRuntimeClass classCWnd : static變量保存一個對應的CRuntimeClass,這樣系統初始化時應該會存在一一對應關係進行初始化,這樣我們可以通過CRuntimeClass輕鬆找到我們需要用的類。
(2) virtual CRuntimeClass* GetRuntimeClass() const :虛函數子類可進行Override,這樣的好處是,我們可以輕鬆找到子類對CRuntimeClass的最新override,不管我們當時是否使用的基類指針(虛函數是根據虛函數表進行讀取的)。
這也就是虛函數的魅力所在吧!當然,聲明完以後,我們需要去實現它,我們進一步引進了IMPLEMENT_DYNAMIC宏,如下:
#define IMPLEMENT_DYNAMIC(class_name,base_class_name) \
_IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,0xFFFF,NULL)
IMPLEMENT_DYNAMIC是對_IMPLEMENT_RUNTIMECLASS進行的封裝,來看看_IMPLEMENT_RUNTIMECLASS的代碼:
#define _IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,wSchema,pfnNew) \
static char lpsz##class_name[] = #class_name; \
CRuntimeClass class_name::class##class_name = { \
lpsz##class_name,sizeof(class_name),wSchema,pfnNew, \
RUNTIME_CLASS(base_class_name),NULL }; \
static AFX_CLASSINIT _init_##class_name(&class_name::class##class_name); \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return &class_name::class##class_name;} \
涉及到RUNTIME_CLASS(class_name)宏:
#define RUNTIME_CLASS(class_name) \
(&class_name::class##class_name)
涉及到一個AFX_CLASSINIT結構體(運用結構體的構造函數實現CRuntimeClass鏈表連接)
struct AFX_CLASSINIT
{
AFX_CLASSINIT(CRuntimeClass* pNewClass);
{
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pNewClass;
}
};
比如我們同樣用CWnd來做示範:
IMPLEMENT_DYNAMIC(CWnd, CCmdTarget)
我們拆解開看看是啥:static char lpszCWnd[] = "CWnd";
struct CRuntimeClass CWnd::classCWnd = { lpszCWnd, sizeof(szCWnd),wSchema,pfnNew,RUNTIME_CLASS(CCmdTarget),NULL};
static AFX_CLASSINIT _init_CWnd(&CWnd::classCWnd);
CRuntimeClass* CWnd::GetRuntimeClass() const
{ return &CWnd::classCWnd;}
以上即實現了DECLARE_DYNAMIC中定義的成員和函數,並將鏈表增長。
class CObject
{
public:
virtual CRuntimeClass* GetRuntimeClass() const;
public:
static CRuntimeClass classCObject;
};
static char lpszCObject[] = "CObject";
struct CRuntimeClass CObject::classCObject = { lpszCObject, sizeof(szCObject), 0xFFFF, NULL, NULL, NULL };
static AFX_CLASSINIT _init_CObject(&CObject::classCObject);
CRuntimeClass* CRuntimeClass::pFirstClass = NULL;
CRuntimeClass* CObject::GetRuntimeClass() const
{
return &CObject::classCObject;
}