CObject 是 MFC 絕大多數類別的根基類,提供了四大核心特性:運行時類型資訊(RTTI)、動態創建、序列化 以及 診斷輸出。理解其結構與實作機制,是掌握 MFC 架構設計精髓的關鍵。
class CObject {
public:
virtual CRuntimeClass* GetRuntimeClass() const;
virtual ~CObject(); // 虛擬解構子
void* PASCAL operator new(size_t nSize); // 診斷用記憶體分配
// ... 除錯版 new/delete 多載
protected:
CObject(); // 預設建構子
private:
CObject(const CObject&); // 禁止複製建構
void operator=(const CObject&); // 禁止指派
public:
BOOL IsSerializable() const;
BOOL IsKindOf(const CRuntimeClass* pClass) const;
virtual void Serialize(CArchive& ar); // 序列化入口
virtual void AssertValid() const; // 診斷
virtual void Dump(CDumpContext& dc) const;
static const AFX_DATA CRuntimeClass classCObject; // 靜態類型資訊
#ifdef _AFXDLL
static CRuntimeClass* PASCAL _GetBaseClass();
#endif
};
關鍵點:五個虛擬函式(解構子、GetRuntimeClass、Serialize、AssertValid、Dump)與 靜態成員 classCObject 共同支撐三大特性。
| 特性 | 宣告巨集(.h) | 實作巨集(.cpp) | 必要條件 |
|---|---|---|---|
| 運行時類型 | DECLARE_DYNAMIC(Class) | IMPLEMENT_DYNAMIC(Class, Base) | — |
| 動態創建 | DECLARE_DYNCREATE(Class) | IMPLEMENT_DYNCREATE(Class, Base) | 需有預設建構子 |
| 序列化 | DECLARE_SERIAL(Class) | IMPLEMENT_SERIAL(Class, Base, wSchema) | 需有預設建構子、覆寫 Serialize |
層級遞增:序列化 ⊃ 動態創建 ⊃ 運行時類型。啟用高層特性自動包含低層功能。
DECLARE_DYNAMIC:增加靜態 CRuntimeClass classClassName、虛擬 GetRuntimeClass、(DLL 版)靜態 _GetBaseClass。DECLARE_DYNCREATE:含上述全部,再增靜態 CreateObject。DECLARE_SERIAL:含 _DECLARE_DYNCREATE,並將 classClassName 設為非 const、友元重載 operator>>、靜態 AFX_CLASSINIT _init_ClassName 確保模組啟動時註冊類型資訊。struct CRuntimeClass {
LPCSTR m_lpszClassName; // 類別名稱字串
int m_nObjectSize; // 物件大小
UINT m_wSchema; // 序列化版本號(0xFFFF 表不支援)
CObject* (PASCAL* m_pfnCreateObject)(); // 動態創建函式指標
CRuntimeClass* (PASCAL* m_pfnGetBaseClass)(); // 取得基類資訊
CRuntimeClass* m_pBaseClass; // 基類 CRuntimeClass 指標
CRuntimeClass* m_pNextClass; // 模組類別鏈表指標
CObject* CreateObject(); // 封裝動態創建
BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const; // 繼承關係判斷
};
| 成員 | 運行時類型 | 動態創建 | 序列化 |
|---|---|---|---|
| m_lpszClassName | 類別名 | 類別名 | 類別名 |
| m_nObjectSize | sizeof(Class) | sizeof(Class) | sizeof(Class) |
| m_wSchema | 0xFFFF | 0xFFFF | 1,2... (非 0) |
| m_pfnCreateObject | NULL | &Class::CreateObject | &Class::CreateObject |
| m_pfnGetBaseClass | &Class::_GetBaseClass | &Class::_GetBaseClass | &Class::_GetBaseClass |
| m_pBaseClass | &Base::classBase | &Base::classBase | &Base::classBase |
CObject::classCObject 初始化為 {"CObject", sizeof(CObject), 0xFFFF, NULL, &CObject::_GetBaseClass, NULL}。
CObject* CRuntimeClass::CreateObject() {
if (m_pfnCreateObject == NULL) { TRACE(...); return NULL; }
CObject* pObject = NULL;
TRY { pObject = (*m_pfnCreateObject)(); } END_TRY
return pObject;
}
使用靜態成員函式指標繞過 C++ 無法直接以類別名稱創建物件的限制。
BOOL CRuntimeClass::IsDerivedFrom(const CRuntimeClass* pBaseClass) const {
const CRuntimeClass* pClassThis = this;
while (pClassThis != NULL) {
if (pClassThis == pBaseClass) return TRUE;
#ifdef _AFXDLL
pClassThis = (*pClassThis->m_pfnGetBaseClass)();
#else
pClassThis = pClassThis->m_pBaseClass;
#endif
}
return FALSE;
}
靜態 CRuntimeClass 物件在程式啟動前即完成初始化,全程序僅有一份,故指標比較即可確認類型關係。
#define RUNTIME_CLASS(class_name) (&class_name::class##class_name)
快速取得任意類別的 CRuntimeClass*。
CArchive 充當物件與儲存媒體(CFile/Socket)間的二進位流代理,重載 <</>> 處理基本型別與複雜物件。
CRuntimeClass 資訊,再呼叫物件的 Serialize。CRuntimeClass 鏈表中尋找對應類型資訊,動態創建物件,再呼叫 Serialize 還原資料。因此序列化類別必須支援動態類型資訊與動態創建。若直接呼叫 Serialize 而不經 CArchive,則可省略前兩項特性。
// CPerson.h
class CPerson : public CObject {
DECLARE_SERIAL(CPerson)
public:
CPerson() {} // 預設建構子
CString m_name; WORD m_number;
virtual void Serialize(CArchive& ar);
};
// CPerson.cpp
IMPLEMENT_SERIAL(CPerson, CObject, 1) // schema = 1
void CPerson::Serialize(CArchive& ar) {
CObject::Serialize(ar); // 呼叫基類
if (ar.IsStoring()) ar << m_name << m_number;
else ar >> m_name >> m_number;
}
// 使用範例
CPerson a; ASSERT(a.IsKindOf(RUNTIME_CLASS(CPerson)));
CRuntimeClass* pClass = RUNTIME_CLASS(CPerson);
CObject* pObj = pClass->CreateObject(); // 動態創建
ASSERT(pObj->IsKindOf(RUNTIME_CLASS(CPerson)));