MFC类结构-1、CObject类
CObject是“MFC類之母”,由它派生出龐大的類體系。CObject并不是對整個類體系進行語義抽象的結果,它只為所有派生類定義幾種功能特性。由于這幾項功能應用于MFC的大部分類中,成為MFC的普遍現象,有必要認真學習。下面就著重討論這幾項功能特性。
5.1.1? 支持類診斷
CObject類定義了這樣一個虛擬函數:
public:
virtual? void CObject::AssertValid() const
{
???????? ASSERT(this != NULL);
}
派生類可以對它進行重載,通過當前對象的狀態,診斷它的有效性。診斷條件根據需要而定。一般通過ASSERT宏進行診斷,這樣當調試出錯時,程序會在失敗的ASSERT所在代碼行中斷,便于調試。但在程序的發布版中,該診斷無效。
在派生類的重載版本中,出于習慣,一般要首先調用基類的版本對this指針進行檢查。例如:
class CDate :public CObject
{
public:
???????? CDate(unsigned int year,unsigned int month,unsigned int day)
???????? {
????????????????? m_Year=year;m_Month=month;m_Day=day;
?????????????????????????? MonthDays[0]=31;
?????????????????????????? MonthDays[1]=29;
?????????????????????????? MonthDays[2]=31;
?????????????????????????? MonthDays[3]=30;
?????????????????????????? MonthDays[4]=31;
?????????????????????????? MonthDays[5]=30;
?????????????????????????? MonthDays[6]=31;
?????????????????????????? MonthDays[7]=31;
?????????????????????????? MonthDays[8]=30;
?????????????????????????? MonthDays[9]=31;
?????????????????????????? MonthDays[10]=30;
?????????????????????????? MonthDays[11]=31;
???????? }
???????? void AssertValid() const
???????? {
????????????????? CObject::AssertValid();
????????????????? ASSERT(m_Year>0&&m_Month>0&&m_Month<=12&&m_Day>0);
????????????????? ASSERT((m_Month==2)?
????????????????????? ((m_Year%100 && !(m_Year%4)|| !(m_Year%400))?(m_Day<=29):(m_Day<=28))
????????????????????????? :(m_Day<=MonthDays[m_Month-1]));
???????? }
???????? unsigned int GetYear()const;
???????? unsigned int GetMonth()const;
???????? unsigned int GetDay()const;
???????? inline void SetDate(unsigned int year,unsigned int month,unsigned int day);
private:
???????? unsigned int m_Year,m_Month,m_Day;
???????? unsigned char MonthDays[12];
};
void gFun()
{
CDate date(2000,2,29);
//校驗date對象的有效性
date.AssertValid();
}
應該注意到,AssertValid()虛函數以const關鍵字修飾,所以該函數不能直接或間接改變類成員的狀態。
同時,為了在調試時輸出類實例的相關信息,CObject類又實現了這樣一個虛函數:
public:
virtual void CObject::Dump(CDumpContext& dc) const
{
???????? dc << "a " << GetRuntimeClass()->m_lpszClassName <<
????????????????? " at " << (void*)this << "\n";
???????? UNUSED(dc); //該語句無實際操作,意在標識發布版中這段代碼無效
}
在說明該函數的作用之前,首先復習VC++的調試輸出功能。VC++的強大調試功能是一個亮點,在調試過程中可以隨時調用TRACE語句將診斷信息輸出到調試窗口,避免了使用MessageBox()帶來的麻煩。而這一切在發布版中會自動清除。
TRACE類似于C語言的printf語句,適合一般的數據類型輸出,而在C++環境下,應該提供類實例的診斷輸出工具。是的,MFC為程序員定義了這樣的工具:CDumpContext 類,并定義了該類的一個實例afxDump。程序員可以直接使用afxDump對象,CDumpContext 類重載了各種數據類型的插入運算符“<<”,包括CObject引用和指針,使用方法就同C++的標準輸出函數cout一樣。CDumpContext也將信息輸出到調試窗口,也只能在Debug版本中有效,如果以CObject或其派生類的對象作參數,輸出的是對象的名稱和地址。例如:
?
{
CDate date(2000,2,29);
date.AssertValid();
afxDump<<date<<"year="<<date.GetYear();
}
輸出結果:
a CObject at $12F528
year=0x7D0
如果在類CDate的定義和實現中分別調用DECLARE_DYNAMIC宏和IMPLEMENT_ DYNAMIC宏,輸出的是真實的類名稱,而不是基類CObject。
那么CObject::Dump()虛函數與afxDump診斷輸出有何聯系呢?從該虛函數的實現代碼可知,前者調用后者,輸出類運行時名稱和對象地址。深入CDumpContext的類代碼又會發現,重載的CObject對象插入運算符又調用了實參的虛函數Dump()。所以,只要在CDate類中重載虛函數Dump(),輸出必要的診斷信息,而后執行語句
afxDump<<date;
即可輸出全部診斷信息,因為該語句調用了重載的Dump()虛函數。示例5.1是上例的改進代碼:
示例清單5.1
class CDate :public CObject
{
???????? DECLARE_DYNAMIC(CDate)???
public:
???????? CDate(unsigned int year,unsigned int month,unsigned int day);
???????? void AssertValid() const;
#ifdef _DEBUG
???????? void Dump(CDumpContext& dc) const
???????? {
????????????????? dc<<"virtual function Dump in CDate called\n";
????????????????? //調用基類的虛函數輸出類名和地址
????????????????? CObject::Dump(dc);
????????????????? dc<<"year="<<GetYear()<<"\n";
????????????????? dc<<"month="<<GetMonth()<<"\n";
????????????????? dc<<"day="<<GetDay()<<"\n";
???????? }
#endif
???????? unsigned int GetYear()const{return m_Year;}
???????? unsigned int GetMonth()const{return m_Month;}
???????? unsigned int GetDay()const{return m_Day;}
???????? void SetDate(unsigned int year,unsigned int month,unsigned int day);
private:
???????? unsigned int m_Year,m_Month,m_Day;
???????? unsigned char MonthDays[12];
};
???????? IMPLEMENT_DYNAMIC(CDate,CObject)
void gFun()
{
CDate date(2000,2,29);
date.AssertValid();
afxDump<<date;
}
函數gFun()輸出:
virtual function Dump in CDate called
a CDate at $12F528
year=0x7D0
month=0x2
day=0x1D
重載Dump()還要注意兩點:一是要將函數體用#ifdef _DEBUG/#endif括起,二是不要直接或間接改變對象的狀態。
5.1.2? 提供運行時類信息
類的運行時信息包括類名稱、尺寸,以及不知道類名稱而創建類實例的能力。如果需要隨時訪問CObject派生類對象的類名,通過定義虛函數很容易實現。例如:
class CObject
{
public:
???????? virtual char * GetClassName()const
???????? {return m_ClassName;}
static char m_ClassName[];
?......
};
char CObject::m_ClassName[]="CObject";
class CDate:public CObject
{
public:
???????? virtual char * GetClassName()const
???????? {return m_ClassName;}
static char m_ClassName[];
};
char CDate::m_ClassName[]="CDate";
由此可知,只要每個派生類都定義一個靜態字符串儲存類名,并重載虛函數GetClass Name()返回該串即可。
對于類尺寸通過虛函數或靜態成員也可以提供。那么,不知道類名稱而創建類的實例,如何實現呢?我們知道,類的靜態成員與類的實例無關,只要將類的創建信息存儲在靜態成員中,調用類的靜態成員就可以創建類的實例了。見下面的示例5.2。
示例清單5.2
#include "stdio.h"
class CObject
{
public:
???????? virtual void Declare()const
???????? {
???????? printf("I am CObject\n");
???????? }
};
//定義函數指針
typedef CObject* (*FUN_CreateObject)();
class CDate:public CObject
{
public:
???????? CDate(){}
???????? virtual void Declare()const
???????? {
???????? printf("I am CDate\n");
???????? }
//定義靜態函數,用于創建該類的實例
???????? static CObject* CreateObject()
???????? {????????? return new CDate();??????? }
???????? static FUN_CreateObject pfunCreateObject;
};
//靜態的函數指針指向創建類實例的靜態函數
FUN_CreateObject CDate::pfunCreateObject=CDate::CreateObject;
void gCreateHelper(FUN_CreateObject pfunCreateObject)
{
//不知道類名稱,間接創建該類
if(NULL==pfunCreateObject)
{
printf("create object failed\n");
return;
}
CObject*pObject=(CObject*)pfunCreateObject();
if(NULL!=pObject)
{
//調用虛函數,驗證所創建的類
pObject->Declare();
delete pObject;
}
}?
int main(int argc, char* argv[])
{
???????? gCreateHelper(CDate::pfunCreateObject);
???????? return 0;
}
程序輸出:
I am CDate
以上,我們撇開MFC自行解決了運行時類信息問題。下面學習MFC是如何實現的。
為了簡化代碼,MFC使用了一系列宏定義,提供運行時類信息。在上文對類診斷的討論中,已經使用過DECLARE_DYNAMIC宏和IMPLEMENT_DYNAMIC宏,輸出了類名稱。這兩個宏的作用是,在類中插入一個靜態的CRuntimeClass結構型成員。該結構的定義簡寫如下:
struct CRuntimeClass
{
???????? LPCSTR m_lpszClassName;
???????? int m_nObjectSize;
???????? CObject* (PASCAL* m_pfnCreateObject)();
???????? CObject* CreateObject();
???????? BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;
};
同時定義了一個虛函數用于返回這個靜態CRuntimeClass結構型成員的指針,如下:
virtual CRuntimeClass* GetRuntimeClass() const;
并由IMPLEMENT_DYNAMIC宏將類名稱存儲在該結構成員的m_lpszClassName成員中,類尺寸存儲在m_nObjectSize成員中,所以只要調用對象的
GetRuntimeClass()->m_lpszClassName;
就得到了對象的類名稱等運行時信息。被插入的CRuntimeClass型靜態成員被命名為class##class_name,即如果類名為CDynaClass,則該成員名為classCDynaClass。除使用虛擬函數GetRuntimeClass(),在知道類名的前提下,還可以使用RUNTIME_CLASS(class_name)宏取得這個CRuntimeClass指針。例如:
ASSERT(RUNTIME_CLASS(CDynaClass)->m_lpszClassName=="CDynaClass");
DECLARE_DYNAMIC宏定義簡寫如下:
#define DECLARE_DYNAMIC(class_name) \
public: \
static const AFX_DATA CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const; \
對于動態創建(即不知道類名而創建該類),MFC也提供了相應的宏定義,即DECLARE_DRNCREATE和IMPLEMENT_DRNCREATE(或DECLARE_SERIAL和IMPLEMENT_SERIAL)。該宏除包含#######_DYNAMIC宏的所有功能外,還在類中插入一個靜態成員函數CreateObject(),其功能就如同示例5.2中所定義的一樣。觀察Cruntime Class結構可知,該結構包含一個函數指針型成員m_pfnCreateObject,其類型也同示例5.2中的CDate::pfunCreateObject相似。實際上IMPLEMENT_DRNCREATE宏正是將插入的靜態成員函數CreateObject(),賦給插入的靜態CRuntimeClass實例的m_pfnCreateObject指針。這樣,只要取得這個CRuntimeClass指針,即使不知道類名,也可以創建該類的實例了。
應該注意到,CRuntimeClass結構也包含一個CreateObject()成員函數,該函數直接調用m_pfnCreateObject所指向的函數。例如:
CObject* CRuntimeClass::CreateObject()
{
???????? if (m_pfnCreateObject == NULL)
???????? {??????? return NULL;
???????? }
???????? CObject* pObject = NULL;
???????? TRY
???????? {??????? //創建類實例
????????????????? pObject = (*m_pfnCreateObject)();
???????? }
???????? END_TRY
???????? return pObject;
}
所以,在實際編程中往往調用CRuntimeClass::CreateObject()創建對象,而不是Cruntime Class::m_pfnCreateObject()。例如:
CRuntimeClass *pRunInfo=RUNTIME_CLASS(CDynaClass);
CObject *pObj=(CObject*)pRunInfo->CreateObject();
這種所謂的動態創建特性,進一步提高了類的抽象程度,可以利用它編寫較為通用的代碼,例如示例5.2中的全局函數gCreateHelper()。實現動態創建特性的類需要有無參的構造函數。
MFC的文檔模板類CDocTemplate正是運用動態創建特性實現的通用代碼,它可以創建出各種文檔/視圖結構。對下面的語句你肯定非常熟悉。
CSingleDocTemplate* pDocTemplate;
???????? pDocTemplate = new CSingleDocTemplate(
????????????????? IDR_MAINFRAME,
//將下面3個動態類的CRuntimeClass靜態成員指針作為構造參數
????????????????? RUNTIME_CLASS(CMyDoc),
????????????????? RUNTIME_CLASS(CMainFrame),
????????????????? RUNTIME_CLASS(CMyView));
雖然運行時類信息是通過MFC宏提供的,但離不開CObject類的支持。相關函數的參數都是以基類CObject為基礎的,CObject除定義了相關的靜態成員和虛函數,還重載了new和delete運算符支持動態創建。
5.1.3? 支持類的連載
類的連載就是能夠將對象的當前狀態保存在磁盤文件上(或以其他方式保存),同時也能夠將保存在文件中的對象信息加載到對象內存中,恢復對象的狀態。
CObject支持的連載合成了CArchive類,如同診斷輸出中使用的CDumpContext類。該類重載了“<<”和“>>”運算符,分別用于存儲對象狀態和恢復對象狀態。該類的存儲和讀取文件的功能是通過合成CFile類對象實現的。下面是其構造函數:
CArchive(CFile* pFile, UINT nMode, int nBufSize = 4096, void* lpBuf = NULL);
參數1是文件指針,除磁盤文件外,也可以打開命名管道或端口設備,或者通過CSocketFile進行網絡傳輸。首先打開該文件,然后調用CArchive構造函數;在完成存儲或加載后,先關閉CArchive對象,再關閉文件。參數2是連載模式,即存儲(CArchive::store)或加載(CArchive::load)。一個CArchive對象只能用于一種模式。參數3是臨時緩沖的尺寸。
CObject類為連載定義了虛函數:
virtual void Serialize(CArchive& ar)
派生類可以對它重載,實現連載。下例是在示例5.1的基礎上編寫的連載演示。
virtual void CDate::Serialize(CArchive& ar)
?{
?? if(ar.IsLoading())
?? {
?? ar>>m_Year>>m_Month>>m_Day;
?? }
?? else
?? ar<<m_Year<<m_Month<<m_Day;
?}
void gStoreDate()
{
CFile fp;
CDate date(2000,2,29);
date.AssertValid();
afxDump<<date;
if(fp.Open("CDate.dat",CStdioFile::modeWrite|CFile::modeCreate))
{
???????? CArchive ar(&fp,CArchive::store);
???????? date.Serialize(ar);
???????? ar.Close();
???????? fp.Close();
}
}
void gLoadDate()
{
CFile fp;
CDate date(0,0,0);
if(fp.Open("CDate.dat",CStdioFile::modeRead))
{
???????? CArchive ar(&fp,CArchive::load);
???????? date.Serialize(ar);
???????? ar.Close();
???????? fp.Close();
}
date.AssertValid();
afxDump<<date;
}
如果gStoreDate()和gLoadDate()函數先后執行,二者的診斷輸出相同。
為方便實現連載,MFC也提供了宏定義的支持,即DECLARE_SERIAL/IMPLEMENT_ SERIAL,該宏除實現連載還包括DECLARE_DRNCREATE/IMPLEMENT_DRNCREATE宏的所有功能。在CObject派生類中定義SERIAL宏、重載Serialize()虛函數、有默認構造器的3個前提下,連載該類的對象時,可以直接使用CArchive重載的“<<”和“>>”運算符操作對象。例如:
CDate date(0,0,0);
???????? CArchive ar(&fp,CArchive::load);
???????? Ar>>date;
其形式同
afxDump<<date;
一樣。
注意,直接調用“<<”或“>>”運算符連載對象,與調用對象的Serialize()虛函數是有區別的。前者不僅連載對象的狀態,還連載對象的類運行時信息。
在這種方式下,可以將磁盤文件中的類信息加載到還沒有指向有效地址的對象指針中,對象的創建工作自動完成(因為該類已支持自動創建),但對象的釋放要手動進行。例如:
{
CFile fp;
CDate *date=NULL;
if(fp.Open("CDate.dat",CStdioFile::modeRead))
{??????? CArchive ar(&fp,CArchive::load);
???????? ar>>date;
???????? //date->Serialize(ar);
???????? ar.Close();
???????? fp.Close();
}
if(date!=NULL)
{
date->AssertValid();
afxDump<<date;
delete date;
} }
但無論以何種方式連載類對象,存儲和加載的方式要一致。
在文檔/視圖框架程序中,MFC框架(本書中的框架指MFC體系結構)會在存儲文件(ID_FILE_SAVE或ID_FILE_SAVE_AS)和打開文件(ID_FILE_OPEN)時,調用文檔類對象的連載操作,分別進行存儲和加載。該功能無需顯示定義CArchive對象,文檔類也無需使用_SERIAL宏。
轉載于:https://www.cnblogs.com/carekee/articles/2041405.html
總結
以上是生活随笔為你收集整理的MFC类结构-1、CObject类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c++自定义的数据库类
- 下一篇: 到底什么是MiddleWare(中间件)