MFC原理第二講——運行時類識別、類繼承關系、類創建20171220
一.類的繼承關系
昨天MFC原理第一講中講了C**App類,其作用是對程序進行初始化,那么MFC單文檔版工程最后生成的窗口中有主窗口、視圖、菜單、工具欄等,還有打開文檔的功能,對應這些界面會有相應的類來操作,分別是CMainFrame、C**View、C**Doc,菜單和工具欄的操作歸於CMainFrame中,**代表用戶輸入的工程名稱。CMainFrame、C**View、C**Doc的類繼承關系圖如下:


在這些類中,CMainFrame負責主框架,C**View負責視圖,C**Doc負責文檔數據,在C**View類中有個函數C**Doc* GetDocument(),是獲取文檔類指針。C**View和C**Doc分開是為了降低耦合性,對編譯器更新和數據管理都很有幫助。C**Doc也是CCmdTarget的派生類,因為其中也要消息處理,如打開文件的雙擊操作。對於MFC多文檔版工程,會多加一些文檔和視圖。
有的大游戲的界面設計中會將視圖分層,每層有多個視圖,頂層覆蓋。
二.運行時類識別(CRuntimeClass)
1.運行時類識別實現
程序運行時,平常寫的類不會知道它自己的名稱和它自己繼承自哪個類,當然如果經過特殊處理還是能讓類做到識別它自己的名稱和它自己繼承自哪個類的,這個識別過程就稱為運行時類識別。運行時類識別的實現機制是在自己的類中用一個結構體保存自己類的名稱和父類的名稱,MFC中實現運行時類識別時用到的保存自身信息和創建類對象的結構體CRuntimeClass如下:
struct CRuntimeClass { // Attributes LPCSTR m_lpszClassName; int m_nObjectSize; UINT m_wSchema; // schema number of the loaded class CObject* (__stdcall * m_pfnCreateObject)(); // NULL => abstract class CRuntimeClass* m_pBaseClass; // Operations CObject* CreateObject(); BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const; // CMyRuntimeClass objects linked together in simple list CRuntimeClass* m_pNextClass; // linked list of registered classes };
以上結構體在類中使用時,是定義成靜態成員函數的,因為這個結構體是描述類的信息,所有對象共用,並且作為同全局變量生命周期,初始化會很早,通過類域而非類對象和類對象指針就可使用,其中有6個成員變量,1)m_lpszClassName是使用該結構體的類的名稱,2)m_nObjectSize是使用該結構體的類的字節大小,3)m_wSchema是版本號,4)CObject* (__stdcall * m_pfnCreateObject)()是一個創建類對象的函數指針,會在靜態結構體賦初值時將使用其的類中的一個靜態創建對象函數的指針賦給它,該函數指針的存在是為了實現通過類名創建類對象,5)m_pBaseClass是使用該結構體的類的基類中該結構體靜態成員指針,6)m_pNextClass編譯器沒用,一般填NULL,另外還有兩個成員函數,1)CObject* CreateObject()的作用是通過使用該結構體的類類名稱就創建類使用該結構體的類對象,2)BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const是判斷pBaseClass是否是當前使用該結構體的類的父類中的CRuntimeClass成員指針。
2.MFC運行時類識別實例
1)在最初始基類CObject中使用,截取其中一段成員變量和成員函數如下:
//*.h文件 class CObject { public: CObject(); virtual ~CObject(); static CRuntimeClass classCobject; virtual CRuntimeClass* GetRuntimeClass() const; BOOL IsKindOf(const CRuntimeClass* pClass) const; }; //*.cpp文件 CRuntimeClass CObject::classCObject = {"CObject", sizeof(CObject), NULL, NULL, NULL, NULL}; #define MYRUNTIME_CLASS(class_name) ((CMyRuntimeClass*)(&class_name::class##class_name)) CMyRuntimeClass* CMyobject::GetRuntimeClass() const { return RUNTIME_CLASS(CMyobject); } BOOL CMyobject::IsKindOf(const CMyRuntimeClass* pClass) const { CMyRuntimeClass* pClassThis = GetRuntimeClass(); return pClassThis->IsDerivedFrom(pClass); }
試想一個類要識別自己的名稱和繼承關系是不是都要加上以上代碼呢?既然都得加,那么可不可以寫成宏,方便書寫也方便更改,定義宏如下:
//在該文檔中,有的名稱前面加了MY或My,其實與沒加在本文是一個意思,只是加到程序中都統一 //1) #define MYDECLARE_DYNAMIC(class_name) \ public: \ static const CMyRuntimeClass class##class_name; \ virtual CMyRuntimeClass* GetRuntimeClass() const; \ //2) #define MYDECLARE_DYNCREATE(class_name) \ MYDECLARE_DYNAMIC(class_name) \ static CMyobject* __stdcall CreateObject();\ //給出創建自己類對象的靜態成員函數,至於最后創不創建由用戶決定 //3) #define MYRUNTIME_CLASS(class_name) ((CMyRuntimeClass*)(&class_name::class##class_name)) //4) #define MYIMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) \ const CMyRuntimeClass class_name::class##class_name = { \ #class_name, sizeof(class class_name), wSchema, pfnNew, \ MYRUNTIME_CLASS(base_class_name), NULL }; \ CMyRuntimeClass* class_name::GetRuntimeClass() const \ { return MYRUNTIME_CLASS(class_name); } //5) #define MYIMPLEMENT_DYNCREATE(class_name, base_class_name) \ CMyobject* __stdcall class_name::CreateObject() \ { return new class_name; } \ MYIMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \ class_name::CreateObject) //6) #define CREATE_OBJECT(class_name)\ (class_name*)MYRUNTIME_CLASS(CMyMainFrame)->CreateObject();
以上六個宏,分別說明如下:1)定義靜態CMyRuntimeClass結構體成員變量class##class_name,如classCWnd,和申明返回CMyRuntimeClass指針的虛函數,2)在1)中宏基礎上,再定義一個靜態創建對象的函數,實現在5)宏中,3)返回class_name類中的CMyRuntimeClass成員指針,4)給靜態CMyRuntimeClass結構體成員變量做初始化,並實現成員函數GetRuntimeClass,5)先實現CMyobject* __stdcall class_name::CreateObject(),在其中new自身類對象,並返回,一個靜態成員函數可以做到,如果是非靜態成員函數,則肯定是不行的,然后加上MYIMPLEMENT_RUNTIMECLASS,6)我自己寫的,用於實現通過類名,產生一個類對象指針。
通過類名產生類對象的過程說明:class_name類中定義了靜態CMyRuntimeClass結構體成員變量和靜態創建對象的函數,靜態創建對象的函數作用是new class_name類並且返回,然后將該函數指針賦給CMyRuntimeClass結構體成員變量中的CObject* (__stdcall * m_pfnCreateObject)()成員變量,也許有的人會覺得,這里不是類成員函數嗎,指針可以這樣賦值嗎?其實對於靜態成員函數而言,只是加了個類域,只要是同一類域下便可賦值,靜態CMyRuntimeClass結構體成員變量和靜態創建對象的函數顯然是屬於同一類域中。最后可以通過CMyRuntimeClass結構體成員變量中的CObject* CreateObject()函數中使用m_pfnCreateObject函數指針創建對象。
技術關鍵點:1)使用了靜態成員函數,2)使用了宏,節省了大量代碼的書寫,也方便改代碼。寫完宏后,在每個除CObject外的MFC類的申明中加DECLARE_DYNCREATE(class_name),在實現中加MYIMPLEMENT_DYNCREATE(class_name,base_class_name)即可完成所有MFC類的運行時類識別和繼承關系和創建類對象。在MFC中創建新類時,提示是創建一般類還是MFC類的區別就在於要不要繼承MFC類並且加宏到申明和實現中。