MFC 類型識別與動態創建

If you have derived your class from< id="alink_3" type="application/x-oleobject" classid="clsid:adb880a6-d8ff-11cf-9377-00aa003b7a11">

CObject and used the DECLARE _DYNAMIC and IMPLEMENT_DYNAMIC , the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE , or the DECLARE_SERIAL and IMPLEMENT_SERIAL macros explained in the article CObject Class: Deriving a Class from CObject , the CObject class has the ability to determine the exact class of an object at run time.

摘自MSDN,意思是你的類如果從CObject派生並且用了DECLARE _DYNAMIC /IMPLEMENT_DYNAMIC , DECLARE_DYNCREATE  / IMPLEMENT_DYNCREATE , 或者 DECLARE_SERIAL /IMPLEMENT_SERIAL 宏,那麼你的類就能夠提供運行時類型信息.

很多朋友一看RTTI就知道是"運行時類型信息"的意思,但並不知道什麼時候RTTI會用得上,在MFC中,與C++的機制是不一樣的,下面我寫點關於RTTI的實現.肯定有很多錯誤的,還是那句話,歡迎拍磚指導.

什麼時候用RTTI? 在這樣的代碼中:

class base{.....};

class deri: public base{......};

void func()

{

            base*p=new deri;

}

這就是所謂的C++動態創建,base指針指着一個deri類的對象, 有時候,我們需要知道deri的類型,這時就要用到RTTI,到底什麼時候要用到呢?比如,我們要根據對象的類型做不同的處理,如打印一行信息,另外如永久保存,數據讀寫時,就需要類型識別! 

MFC中RTTI的實現依賴於CRunTime類,下面的是該類的一些成員:
CRuntimeClass Class Members
CreateObject()   Creates an object during run time.
FromName()   Creates an object during run time using the familiar class name.
IsDerivedFrom()  Determines if the class is derived from the specified class.

CRuntimeClass Data Members
m_pBaseClass   A pointer to the CRuntimeClass structure of the base class.
m_lpszClassName  The name of the class.
m_nObjectSize   The size of the object in bytes.
m_pfnCreateObject  A pointer to the function that dynamically creates the object.
m_pfnGetBaseClass  Returns the CRuntimeClass structure (only available when dynamically linked).
m_wSchema   The schema number of the class. //版本號!!
m_pNextClass  A pointer to the next node of the object list.
 注: 侯俊傑先生在其<<深入淺出MFC>>中,對RTTI進行仿真的過程中爲CRuntimeClass加入了成員 m_pFirstClass,導致很多讀者以爲MFC中該類真有一個這樣的成員,甚至沒看MFC的CRuntimeClass源代碼就以訛傳訛,實在不嚴 謹.

本來,在C/C++中有一個typeid操作符(類似於sizeof),它可以判斷對象的類型,用法如下:
 Devi objDevi;  //Devi是本人自定義的一個類;

 if (typeid(Devi)==typeid(objDevi))
 {
  cout<<"objDevi is a object of class Devi"<<endl;
 }
 const type_info& tpdevi=typeid(objDevi);
 cout<<tpdevi.name()<<endl;               //這裏得出是class Devi的類型,用於人識別
 cout<<tpdevi.raw_name()<<endl;   //這裏返回的是內存表示,用於計算機識別

 可惜的是typeid比MFC的RTTI晚出現, MFC中的RTTI沒有使用typeid,而是用了上面提到的一系列的宏,什麼要求用什麼宏,MSDN同樣有說明:

Macros Used for Serialization and Run-Time Information

 


Macro used

CObject::IsKindOf
CRuntimeClass::
CreateObject
CArchive::operator>>
CArchive::operator<<
Basic CObject functionality No No No
DECLARE_DYNAMIC Yes No No
DECLARE_DYNCREATE Yes Yes No
DECLARE_SERIAL Yes Yes Yes

僅判斷類型,用DECLARE_DYNAMIC足矣, 要動態創建對象,得用 DECLARE_DYNCREATE, 而如果要串行化,則得用 DECLARE_SERIAL宏.  這裏只要求判斷類型, 做法是: 每個從CObject派生的類都有一個CRuntimeClass對象與之關聯,用CRuntimeClass的對象作爲結點,組成一條鏈表,該鏈表就是 侯俊傑先生說的"類別型錄網",結點的指針從上面的CRuntimeClass成員列舉中可以看到.當MFC中要確定對象的類型,過程爲:
 用 DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC兩個宏生成對象對應的CRuntimeClass對象並插入到鏈表中,再通過宏 RUNTIME_CLASS得到類的CRuntimeClass對象class***,然後使用CRuntimeClass的成員函數 CreateObject創建一個該類的對象.

每個從CObject派生來的類都有一個CRuntimeClass* GetRuntimeClass( ) const函數,用來返回一個CRuntimeClass對象,如何在各個類之中插入CRuntimeClass對象,並且指定 CRuntimeClass對象的內容及CRuntimeClass對象的鏈接?MFC用了兩個宏實現了這些工作,即DECLARE_DYNAMIC(類 名)和IMPLEMENT_DYNAMIC(類名,基類名)。

而CObject的另一個函數IsKindOf被用來進行運行時識別工作,自從有了IsKindOf,生活就變得美好了:
BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
{
 CRuntimeClass* pClassThis = GetRuntimeClass();
 return pClassThis->IsDerivedFrom(pClass);
}

 而GetRuntimeClass()的作用是返回與該類關聯的CRuntimeClass類指針:
CRuntimeClass* CObject::GetRuntimeClass() const
{
 return _RUNTIME_CLASS(CObject);
}

 而這個宏RUNTIME_CLASS 就是用來得到一個static const AFX_DATA CRuntimeClass classCObject.  (每個從CObject派生的類如CMainFrame都有成員classCMainFrame,這個class***成員就是與MFC類相關的那個 CRuntimeClass對象!!).

 解決問題的關鍵還在於上面的IsDerivedFrom函數:
BOOL CRuntimeClass::IsDerivedFrom(const CRuntimeClass* pBaseClass) const
{
   const CRuntimeClass* pClassThis = this;
   while (pClassThis != NULL)
  {
   if (pClassThis == pBaseClass) return TRUE;         //從pBaseClass派生則返回,否則繼續循環,萬一不行,則返回FALSE;
   pClassThis = pClassThis->m_pBaseClass;
  }
   return FALSE; // walked to the top, no match
}

從宏觀上說,IsKindOf函數判斷了一個MFC對象(而不是類)的類型!!

結論: 如果你的類派生自CObject且使用了 DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC宏,那麼,你的類就能夠調用IsKindOf方法, 而如果調用IsKindOf方法, 則能夠獲取該類的類型信息(如果不調用IsKindOf函數,自然也就沒從獲取類型信息了).


RTTI並非專門用於動態創建,但可以用於動態創建, C++動態創建就是 char *p=new char(65) 這種! MFC中的動態創建主要是指CObject/CWnd/CCmdTarget/.../CMyView等的創建過程,用了CRuntimeClass,機 制上與C++是不同的.

MFC的動態創建過程:
      1、定義一個不帶參數的構造函數,因爲我們是用CreateObject()動態創建,它只有一條語句就是return new XXX,不帶任何參數。

   2、類說明中使用DECLARE_DYNCREATE(CLASSNMAE)宏;和在類的實現文件中使用IMPLEMENT_DYNCREATE(CLASSNAME,BASECLASS)宏,這個宏完成構造CRuntimeClass對象,並加入到鏈表中。

         3、使用時先通過宏RUNTIME_CLASS得到類的RunTime信息,然後使用CRuntimeClass的成員函數CreateObject創建一個該類的實例。

   4、CObject* pObject = pRuntimeClass->CreateObject();//完成動態創建

總結: MFC動態創建的最終動作是CRuntimeClass::CreateObject();獲得類型信息只是過程的一環.

再簡單說一下串行化,有一個有趣的事實:
#define _DECLARE_DYNCREATE(class_name)  _DECLARE_DYNAMIC(class_name)  static CObject* PASCAL CreateObject();

#define DECLARE_SERIAL(class_name) _DECLARE_DYNCREATE(class_name) AFX_API friend CArchive& AFXAPI operator>>(....);
 
 可 簡單地理解爲 DECLARE_DYNCREATE 宏是 DECLARE_DYNAMIC 宏與 CreateObject函數的合, DECLARE_SERIAL 是 DECLARE_DYNCREATE  與操作符operator>>的合. 這裏的operator>>是IMPLEMENT_SERIAL宏重載的.因此,MSDN中也看到了,要使一個類可串行化,使用 DECLARE_SERIAL和IMPLEMENT_SERIAL宏只是一步而已,還要重載Serialize等工作.

 總結: 看CRuntimeClass的源代碼知道,CRuntimeClass還有兩個函數Load和Store,這兩個函數的實現中還調用了CArchive 的Read和Write方法,但是要說明的是,CRuntimeClass的Load/Store在文件的串行化中,只是做了一些輔助工作,如判斷是否第 一次出現,記錄版本號,記錄文件名等,真正實現串行化的還是CArchive的operator<</operator>>以及 其他一些方法.

 CRuntimeClass對象只是MFC類的一個成員變量,它記錄了本MFC類的一些信息,幫助本MFC類實現一些功能,僅此而已.

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