class A;
class B:public A;
A a;
B b;
現在有一個指針 class *pA 它指向一個對象, 請問你怎么知道pA指向的是a對象還是b對象,這是如果有類型信息,我們就可以知道pA到底是什麼對象, 其實,它內部的實現原理是一個字元串,所以,進行這個判斷時,實際上是字元串比較.
DECLARE_DYNCREATE是動態創建的意思.這個有點類似Com的類工廠.
它實際上是用類CRunTime class記錄了類的靜態創建函式的地址.這個特性在很多地方需要使用.就在下面說的DECLARE_SERIAL就是一個經典的例子.
動態創建主要用在 "我不知道要創建的對象就是是什麼類,但是我知道它肯定是從某個基類派生的".
DECLARE_SERIAL是指序列化特性,它是一個完全自動化的存儲機制,它可以將一個對象數組(可能含有A,B,C類的對象)存儲進去,而且能夠根據存儲的情況準確的載入進來,這看起來很簡單, 但是,有一個問題我們必須考慮, 就是怎么寫這個程式,使得載入的時候能夠正確創建相應的A,B,C類的對象呢(注意,這裡是三個不同的類).而且MFC的設計人員當初編寫這個機制的時候根本不知道到底會出現什麼類,也許還會出現D類. 怎么辦呢?
可以肯定,存儲機制中必須要有能夠判斷類種類的代碼.所以,序列化機制DECLARE_SERIAL包含了DECLARE_DYNAMIC,這樣在存儲進入檔案的時候,可以將類名稱存儲到檔案中.
OK,現在我們載入的時候可以知道我們要載入什麼類了,但是,我們又要怎么去創建它呢? 所以DECLARE_SERIAL也包含了DECLARE_DYNCREATE,它用於創建對象.
那么,DECLARE_SERIAL到底有什麼特殊的地方呢?首先,它必須實現operator>>(具體原因可以看看深入淺出,還有版本控制,這樣,我們在處理序列化時,可以很靈活.
首先記住一點,DECLARE_SERIAL最主要的用途是一種智慧型存儲.所以我們可以不用這個智慧型特性.
當我們沒有DECLARE_SERIAL,而有void CMessg::serialize(CArchive& ar)時,我們只能這樣進行存儲
CDocument::Serialize(ar)
{
if (ar.isstoring())
{
//存儲一個對象
pMessg->Serialize(ar);
}
else
{
//必須非常明確的指出New一個 CMessg對象;
pMessg = new CMessg;
pMessg->Serialize(ar);
}
}
在上面這個例子中,根本沒有利用MFC為我們設計的序列化只能機制.
再看下面一個例子
CDocument::Serialize(ar)
{
if (ar.isstoring())
{
//存儲一個對象
ar << pMessg;
}
else
{
//必須非常明確的指出New一個 CMessg對象;
ASSERT(pMessg == NULL);
ar >> pMessg;
}
}
很神奇吧, ar是怎么根據檔案(強調一下,是根據檔案,而不是硬編碼)判斷需要創建什麼類的.
它大概有這么幾個步驟:
1. 因為DECLARE_SERIAL重載了>>操作符,所以可以保證是調用CMessg類的>>函式.
2. >>函式實際上調用的是ar的ReadObject(CRuntimeClass*)函式
3. ReadObject首先從檔案中讀取類判斷信息(可能是一個字元串,可能是一個類索引),得到Class對應的ClassName;
4. 程式的模組狀態中有所有的RuntimeClass的列表,因此,查找對應的程式支持的RuntimeClass(對比ClassName),獲得對應的RuntimeClass;
5. RuntimeClass中含有創建對象的方法CreateObject,調用它,創建對應的對象.這裡,因為CreateObject實際就是 New 一個對象,類似 new CMessg; 所以,為了支持序列化,必須有沒有參數的構造函式.
6. 創建對象之後,調用Seralize(ar),讀入真正的對象的信息.
7. 將對象的指針返回.
8. pMessg就指向一個對應的對象了.
MFC 六大關鍵技術之仿真 DECLARE_SERIAL / IMPLEMENT_SERIAL 宏 |
要將<< 和>> 兩個運運算元多載化,還要讓Serialize 函式神不知鬼不覺地放入類別聲明 之中,最好的作法仍然是使用宏。 類別之能夠進行檔案讀寫動作,前提是擁有動態生成的能力,所以,MFC 設計了兩個宏 DECLARE_SERIAL 和IMPLEMENT_SERIAL: #define DECLARE_SERIAL(class_name) \ DECLARE_DYNCREATE(class_name) \ friend CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb); #define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \ CObject* PASCAL class_name::CreateObject() \ { return new class_name; } \ _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \ class_name::CreateObject) \ CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \ { pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \ return ar; } \ 為了在每一個對象被處理(讀或寫)之前,能夠處理瑣屑的工作,諸如判斷是否第一次 出現、記錄版本號碼、記錄檔案名稱等工作,CRuntimeClass 需要兩個函式Load 和Store struct CRuntimeClass { // Attributes LPCSTR m_lpszClassName; int m_nObjectSize; UINT m_wSchema; // schema number of the loaded class CObject* (PASCAL* m_pfnCreateObject)(); // NULL => Abstract class CRuntimeClass* m_pBaseClass; CObject* CreateObject(); void Store(CArchive& ar) const; static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum); // CRuntimeClass objects linked together in simple list static CRuntimeClass* pFirstClass; // start of class list CRuntimeClass* m_pNextClass; // linked list of registered classes }; 你已經在上一節看過Load 函式,當時為了簡化,我把它的參數拿掉,改為由螢幕上獲 得類別名稱,事實上它應該是從檔案中讀一個類別名稱。至於Store 函式,是把類別名 稱寫入檔案中: // Runtime class serialization code CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive& ar, UINT* pwSchemaNum) { WORD nLen; char szClassName[64]; CRuntimeClass* pClass; ar >> (WORD&)(*pwSchemaNum) >> nLen; if (nLen >= sizeof(szClassName) || ar.Read(szClassName, nLen) != nLen) return NULL; szClassName[nLen] = ~\0~; for (pClass = pFirstClass; pClass != NULL; pClass = pClass->m_pNextClass) { if (lstrcmp(szClassName, pClass->m_lpszClassName) == 0) return pClass; } return NULL; // not found } void CRuntimeClass::Store(CArchive& ar) const // stores a runtime class description { WORD nLen = (WORD)lstrlenA(m_lpszClassName); ar << (WORD)m_wSchema << nLen; ar.Write(m_lpszClassName, nLen*sizeof(char)); } class CScribDoc : public CDocument { DECLARE_DYNCREATE(CScribDoc) ... }; class CStroke : public CObject { DECLARE_SERIAL(CStroke) public: void Serialize(CArchive&); ... }; class CRectangle : public CObject { DECLARE_SERIAL(CRectangle) public: void Serialize(CArchive&); ... }; class CCircle : public CObject { DECLARE_SERIAL(CCircle) public: void Serialize(CArchive&); ... }; 以及在.CPP 檔中做這樣的動作: IMPLEMENT_DYNCREATE(CScribDoc, CDocument) IMPLEMENT_SERIAL(CStroke, CObject, 2) IMPLEMENT_SERIAL(CRectangle, CObject, 1) IMPLEMENT_SERIAL(CCircle, CObject, 1) 然後呢?分頭設計CStroke、CRectangle 和CCircle 的Serialize 函式吧。 當然,毫不令人意外地,MFC 原始碼中的CObList 和CDWordArray 有這樣的內容: // in header files class CDWordArray : public CObject { DECLARE_SERIAL(CDWordArray) public: void Serialize(CArchive&); ... }; class CObList : public CObject { DECLARE_SERIAL(CObList) public: void Serialize(CArchive&); ... }; // in implementation files IMPLEMENT_SERIAL(CObList, CObject, 0) IMPLEMENT_SERIAL(CDWordArray, CObject, 0) 而CObject 也多了一個虛擬函式Serialize: class CObject { public: virtual void Serialize(CArchive& ar); ... } |