MFC六大關鍵技術之仿真學習筆記(三)

       如果你感覺前面兩節一帆風順的話,或許從第三節開始,你會感覺理解有一點點困難,或許讀完整節,你只能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中定義的成員和函數,並將鏈表增長。


對於類根源CObject,我們進行如下處理形成頭部:

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;
}


運用DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC完成了CRuntimeClass鏈表的實現。我們可以通過對鏈表的遍歷來知道類之間的層次和前後關係,對後來的動態創建和類型識別有重要的作用,這不愧是MFC的精華和關鍵啊!





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章