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类相关推荐

  1. MFC 教程【3_CObject类】

    CObject类 CObject是大多数MFC类的根类或基类.CObject类有很多有用的特性:对运行时类信息的支持,对动态创建的支持,对串行化的支持,对象诊断输出,等等.MFC从CObject派生出 ...

  2. error C2248: “CObject::CObject”: 无法访问 private 成员(在“CObject”类中声明)

    原因 首先说明原因,这是由于对CObject对象进行直接拷贝导致的,在MFC中,大部分对象都是继承自​​CObject.然而,​​CObject对象没有实现拷贝构造函数和拷贝赋值运算符,如下图所示. ...

  3. VC++ error C2248: “CObject::CObject”: 无法访问 private 成员(在“CObject”类中声明)

    在使用诸如:CArray或是 CList等类时,经常会出现此错误 此错误的原因是由于自定义的类的数组项时 有一个操作如  Add()  在这个操作中,实际上需要一个 = 操作,但是这个 =操作在 自定 ...

  4. “CObject::operator =”: 无法访问 private 成员(在“CObject”类中声明)

    c++工程编译报错: "CObject::operator =": 无法访问 private 成员(在"CObject"类中声明) 错误无法直接定位源码位置,网 ...

  5. MFC中的CAsyncSocket类实现网络通信

    近年来,利用Internet进行网际间通讯,在WWW浏 览.FTP.Gopher这些常规服务,以及在网络电话.多媒体会议等这些对实时性要求严格 的应用中成为研究的热点,而且已经是必需的了.Window ...

  6. MFC中的CString类使用方法指南

    MFC中的CString类使用方法指南 原文出处:codeproject:CString Management [禾路:这是一篇比较老的资料了,但是对于MFC的程序设计很有帮助.我们在MFC中使用字符 ...

  7. 使用mfc设计长方形的类

    2.使用MFC设计一个长方形类CRectangle,使用对话框输入长方形的长度和宽度,在客户区输出长方形的周长和面积,如图所示. 使用vs创建项目,类型选择"MFC"应用,由于需要 ...

  8. java类结构工具_java类层次结构图工具

    Java主类结构_计算机软件及应用_IT/计算机_专业资料.Java主类结构 谢谢大家! Java主类结构 谢谢大家! +申请认证 文档贡献者 胸兢谙韶硛蠌 中西医 59981 ...... 知识结构 ...

  9. MFC架构之CWinThread类

    我们知道,Windows以事件驱动方式工作,每个WIN32应用程序都至少包含一个消息队列和一个消息泵.消息队列建立在操作系统提供的内存保留区中,消息泵不断搜寻消息队列,将取得的消息分发给应用程序的各个 ...

最新文章

  1. SharePoint 2010 change home page或者default page
  2. TeXLive2019 安装(亲测有效)
  3. 大数据【企业级360°全方位用户画像】标签系统介绍
  4. javascript之闭包理解以及应用场景
  5. 一篇文章看懂Git是什么以及如何简单的上手Git
  6. 15-多容器复杂应用的部署
  7. 狼奔代码生成器使用说明
  8. 将文本文件内容存储在DataSet中的方法总结
  9. Qt将QString转换成ASCII码
  10. xpath 取标签下所有文字内容_如何理解葡萄酒标签上的所有内容(下)
  11. ocr常用数据集介绍
  12. 详解修改BXP服务器IP地址的方法(转)
  13. Servlet容器和IOC容器
  14. 没有躲过的坑--0xC0000005: 读取位置 xxx时发生访问冲突
  15. 在android移动终端运行android应用程序
  16. 各大著名汽车标志图 来历
  17. CSDN富文本编辑器去除空行
  18. java解析outlook的msg邮件(outlook-message-parser)
  19. word无法保存html文件,【修复】Word“文件发生错误”,无法保存文件
  20. 项目规划和方案设计文档的编写

热门文章

  1. 开灯问题 简单模拟法
  2. 尚硅谷图解Java数据结构和算法一
  3. golang中的strings.IndexAny
  4. Nginx入门到实战(4)常见问题
  5. Python OOP
  6. Linux的企业-Mfs高可用corosync+pacemaker+fence+iscci
  7. tkinter之事件绑定
  8. nginx+tomcat动静分离结构
  9. 【转帖】Windows下PostgreSQL安装图解
  10. 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!