MFC中的动态创建与运行时识别
MFC中支持運行時類型識別與動態創建。這更多的是設計問題。理解了其中的設計,可以更快定位用MFC框架開發的代碼。通過研究MFC實現這些的細節,可以更快地掌握其他開發框架的設計思想。
宏定義中的"#"與 “##”
MFC框架大量使用了宏定義來“自動”完成框架的聲明與實現工作。因此首先復習下宏中的#與##功能是有必要的。
“#”
單獨的一個#符號,作用是“字符串化”,比如:
#define ToString(x) #x
則有:
int main()
{
//相當于printf("%s\r\n", “Hello, world!”);
printf("%s\r\n", ToString(Hello, world!);
}
有些框架中會利用這樣的字符串化,形成字符常量到字符串的映射,比如,用這樣的方法制作消息響應表:
struct tagMsgMap {
int nMsg;
char *szMsg;
};
#define MSG_ENTRY(Msg) {Msg, #Msg}
tagMsgMap g_MsgMapEntry[]= {
MSG_ENTRY(WM_COMMAND),
MSG_ENTRY(WM_KEYUP),
…
};
“##”
"##"的作用常被解釋為“連接符”,其實從其存在目的解釋,說成“分隔符”也不算錯。因為設計它的目的之一就是用來解決某些情況下不能用空格來分隔宏定義的問題。比如:
#define PRE(name) prefix_##name
以上的例子,若將##換成空格,則prefix_與name之間會有空格,不符合我們期待的“PRE(haha)等價于prefix_haha”。
總結一下:##起到代替空格的分隔作用,并且在宏展開時,會消除自身前面的空格,如:
#define MACRO1(name, type) type name_##type##type
#define MACRO2(name, type) type name####type##_type
/* 等價于: int name_int_type; */
MACRO1(a1, int);
/* 等價于: int a1_int_type; */
MACRO2(a1, int);
因為##分隔的位置不同,導致編譯器預處理對TOKEN的識別也不同。并且,因為##為消息自身前面的空格,因此以上定義的宏可以寫成以下:
#define MACRO1(name, type) type name_ ##type ##type
#define MACRO2(name, type) type name ## ##type ##_type
設計
MFC的類都有IsKindOf()這個成員函數,用來判斷自己是否屬于某個類(或其派生類),要達到這個目的很簡單,可以將所有的派生關系形成一顆單向樹,識別類型時,從節點向根遍歷并作判斷。
此外,MFC還支持從字符串創建類(這也是序列化的必要條件),這就需要有一個全局的容器,里面裝有所有類的創建函數。
為了滿足以上兩條,MFC設計了CRuntimeClass類來記錄支持RTTI及動態創建的類的信息,其簡化定義如下:
struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName; //類名稱
int m_nObjectSize; //類大小
UINT m_wSchema; // 版本號
CObject* (PASCAL* m_pfnCreateObject)(); // 對象創建函數
CRuntimeClass* m_pBaseClass; //基類信息,各CRuntimeClass通過它連成單向樹
鏈表的頭節點與樹的根節點都是“特殊”節點,需要特殊處理,在MFC框架中CObject就是對應的特殊類。
實現
運行時識別
MFC的運行時識別與DECLARE_DYNAMIC及IMPLEMENT_DYNAMIC兩個宏有關。以下是簡化版:
//放到類聲明中,一個類對應一個全局的CRuntimeClass
#define DECLARE_DYNAMIC(class_name)
public:
static const CRuntimeClass class##class_name;
virtual CRuntimeClass* GetRuntimeClass() const; \
//作為全局(靜態)數據,初始化
#define IMPLEMENT_DYNAMIC(class_name, base_class_name)
const CRuntimeClass class_name::class##class_name = {
#class_name, sizeof(class class_name), 0xFFFF, NULL,
RUNTIME_CLASS(base_class_name), NULL, NULL };
CRuntimeClass* class_name::GetRuntimeClass() const
{ return RUNTIME_CLASS(class_name); }
RUNTIME_CLASS也是一個宏,是為了更方便的得到靜態數據CRuntimeClass:
//這個宏是為了更方便的獲取類里面的靜態數據成員
//如RUNTIME_CLASS(CView) 等價于 ((CRuntimeClass*)(&CView::classCView))
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
建立了單向樹之后,在CObject中添加成員IsKindOf(),即可通過遍歷單向樹來判斷繼承關系:
BOOL CObject::IsKindOf(CRuntimeClass *pClass)
{
CRuntimeClass *pClassThis = GetRuntimeClass();
while(pClassThis != NULL && pClassThis != pClass)
{
pClassThis = pClassThis->m_pBaseClass;
}
}
動態創建
簡單而言,動態創建的最終目的,就是能夠通過字符串來創建對象。這可以在RTTI的基礎上附帶一個創造對象的接口實現:
#define DECLARE_DYNCREATE(class_name)
DECLARE_DYNAMIC(class_name)
static CObject* PASCAL CreateObject();
實現:
#define IMPLEMENT_DYNCREATE(class_name, base_class_name)
CObject* PASCAL class_name::CreateObject()
{ return new class_name; }
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF,
class_name::CreateObject, NULL)
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init)
const CRuntimeClass class_name::class##class_name = {
#class_name, sizeof(class class_name), wSchema, pfnNew,
RUNTIME_CLASS(base_class_name), NULL, class_init };
CRuntimeClass* class_name::GetRuntimeClass() const
{ return RUNTIME_CLASS(class_name); }
以上宏為每一個支持動態創建的類(而不是CRuntimeClass)多聲明、實現了一個靜態CreateObject()接口,用于獲取一個新對象。并且將CreateObject()初始化至對應的CRuntimeClass中。
每一個CRuntimeClass對應一個類,我們也就知道CRuntimeClass中的CreatObject()如何實現了:
CObject * CRuntimeClass::CreateObject()
{
//…各檢查
return (*m_pfnCreateObject)();
}
最終,我們只需要一個函數,它的輸入參數為字符串,其實現中去遍歷CRuntimeClass鏈表并作判斷,當類型一致時就調用CRuntimeClass對象的CreateObject(),就實現了動態創建。這就是CRuntimeClass中的靜態成員函數所做的事。
struct CRuntimeClass
{
//…
static CObject* PASCAL CreateObject(LPCSTR lpszClassName);
/* 實現:
- 從CRuntimeClass鏈表的首節點開始遍歷(pClassThis = pFirst)
- 比較lpszClassName與pClassThis->m_lpszClassName
- 若相等,則調用并返回pClassThis->CreateObject()
*/
};
技術交流群,群內分享視頻、就業資源:C++大學技術協會:145655849
總結
以上是生活随笔為你收集整理的MFC中的动态创建与运行时识别的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【LeetCode笔记】112 113
- 下一篇: 【LeetCode笔记】76. 最小覆盖