注:本篇文章依赖于网上一篇对CObject功能分析的文章(后来才知道是一本书上的),我对其做了进一步详细的注释,一方面使其文章思路更加清晰,一方面又对其中论述进行补充(以我本人的观点看起来觉得不充分详细的地方),最后对其文章中机理分析予以实现(实现CObject),其中加注部分为我新增的理解!
CObject类
CObject是大多数MFC类的根类或基类。CObject类有很多有用的特性:对运行时类信息的支持,对动态创建的支持,对串行化的支持,对象诊断输出,等等。MFC从CObject派生出许多类,具备其中的一个或者多个特性。程序员也可以从CObject类派生出自己的类,利用CObject类的这些特性!
本章将讨论MFC如何设计CObject类的这些特性。首先,考察CObject类的定义,分析其结构和方法(成员变量和成员函数)对CObject特性的支持。然后,讨论CObject特性及其实现机制。
1,CObject的结构
,以下是CObject类的定义:
class CObject
{
public:
//与动态创建相关的函数
virtual CRuntimeClass* GetRuntimeClass() const;
//加注:此函数为virtual函数,用来获取CObject继承体系中的子类自己的CRuntimeClass对象,注意,子类是否具有自己的CRuntimeClass对象取决于子类是否实现了DECLARE_DYNAMIC宏!
//析构函数
virtual ~CObject(); // virtual destructors are necessary
//与构造函数相关的内存分配函数,可以用于DEBUG下输出诊断信息
void* PASCAL operator new(size_t nSize);
void* PASCAL operator new(size_t, void* p);
void PASCAL operator delete(void* p);
#if defined(_DEBUG) && !defined(_AFX_NO_DEBUG_CRT)
void* PASCAL operator new(size_t nSize, LPCSTR/*const char **/ lpszFileName, int nLine);
#endif
//加注:
PASCAL:
PASCAL是windows API的一种函数的调用约定:#define PASCAL pascal,相比较于其他调用约定(__stdcal,___cdecl),这种调用约定使函数的参数在进栈的时候是左面的参数先进栈,右面的参数后进栈,这种方式带来了效率的提高,但是无法支持变参(__stdcal,___cdecl调用约定是参数自右向左入栈,可支持变参)
afx_msg(本来想分析_AFX,转到afx_msg):
//{{AFX_MSG(CMyView)
afx_msg void OnRclickAllstudentsList(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnDestroy();
afx_msg void OnDeleteOffline();
afx_msg void OnBreakConnect();
afx_msg void OnAddBlack();
afx_msg void OnFreeBlack();
afx_msg void OnDblclkListAllstudents(NMHDR* pNMHDR, LRESULT* pResult);
afx_msg void OnDontsendToServer();
afx_msg void OnToolGame();
afx_msg void OnToolIconBigSmall();
afx_msg void OnToolSkin();
afx_msg void OnToolTeach();
afx_msg void OnToolTest();
afx_msg void OnToolTool();
afx_msg void OnFileNewMy();
afx_msg void OnFileOpenMy();
afx_msg void OnFileSaveMy();
afx_msg void OnToolBlack();
afx_msg void OnIcon1();
afx_msg void OnIcon2();
afx_msg void OnViewIconBar();
afx_msg void OnUpdateViewIconBar(CCmdUI* pCmdUI);
afx_msg void OnViewControlBar();
afx_msg void OnUpdateViewControlBar(CCmdUI* pCmdUI);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
我们看到了上面两个函数前面的afx_msg标记,让我们看看究竟afx_msg是什么:
#ifndef afx_msg
#define afx_msg // intentional placeholder
#endif
没什么意思.只是定义了这个符号而已.
1)这个对编译器来说,相当于什么都没有
2)对于人来说,我们可以看到这样的符号.
3)对于类向导来说.这个符号才是有意义的.它是一个消息处理函数的前缀. 类向导生成的消息函数,分发函数,事件响应函数都以这个为前缀. 如果去掉了,向导将不能识别
那么这个没有实际意义的afx_msg的作用究竟在哪里呢?
答:afx_msg向系统声明,有消息将映射到这些函数——所以我们称之为消息映射函数!
BEGIN_MESSAGE_MAP(CMyView, CFormView)
//{{AFX_MSG_MAP(CMyView)
ON_NOTIFY(NM_RCLICK, IDC_LIST_ALLSTUDENTS, OnRclickAllstudentsList)
ON_WM_DESTROY()
ON_COMMAND(IDM_DELETE_OFFLINE, OnDeleteOffline)
ON_COMMAND(IDM_BREAK_CONNECT, OnBreakConnect)
ON_COMMAND(IDM_ADD_BLACK, OnAddBlack)
ON_COMMAND(IDM_FREE_BLACK, OnFreeBlack)
ON_NOTIFY(NM_DBLCLK, IDC_LIST_ALLSTUDENTS, OnDblclkListAllstudents)
ON_COMMAND(IDM_DONTSEND_TO_SERVER, OnDontsendToServer)
ON_COMMAND(ID_TOOL_GAME, OnToolGame)
ON_COMMAND(ID_TOOL_ICON_BIG_SMALL, OnToolIconBigSmall)
ON_COMMAND(ID_TOOL_SKIN, OnToolSkin)
ON_COMMAND(ID_TOOL_TEACH, OnToolTeach)
ON_COMMAND(ID_TOOL_TEST, OnToolTest)
ON_COMMAND(ID_TOOL_TOOL, OnToolTool)
ON_COMMAND(ID_FILE_NEW_MY, OnFileNewMy)
ON_COMMAND(ID_FILE_OPEN_MY, OnFileOpenMy)
ON_COMMAND(ID_FILE_SAVE_MY, OnFileSaveMy)
ON_COMMAND(IDM_TOOL_BLACK, OnToolBlack)
ON_COMMAND(ID_ICON_1, OnIcon1)
ON_COMMAND(ID_ICON_2, OnIcon2)
ON_COMMAND(ID_VIEW_ICON_BAR, OnViewIconBar)
ON_UPDATE_COMMAND_UI(ID_VIEW_ICON_BAR, OnUpdateViewIconBar)
ON_COMMAND(ID_VIEW_CONTROL_BAR, OnViewControlBar)
ON_UPDATE_COMMAND_UI(ID_VIEW_CONTROL_BAR, OnUpdateViewControlBar)
//}}AFX_MSG_MAP
(这两处示例代码未免有点长,但是我找了没有找到短的...)
上面示例代码在AFX_MSG_MAP中(消息映射表)中允许我们将具体的消息映射到相关的函数
//缺省情况下,复制构造函数和赋值构造函数是不可用的
//如果程序员通过传值或者赋值来传递对象,将得到一个编译错误
protected:
//缺省构造函数
CObject();
private:
//复制构造函数,私有
CObject(const CObject& objectSrc); // no implementation
//赋值构造函数,私有
void operator=(const CObject& objectSrc); // no implementation
//加注:注意上面这两个private函数
// Attributes
public:
//与运行时类信息、串行化相关的函数
BOOL IsSerializable() const;//加注:根据CRuntimeClass类型成员变量的m_wShema来判断,用户实现DECLARE_SERIAL宏之后会修改m_wShema的值,默认m_wShema为0xFFFF
BOOL IsKindOf(const CRuntimeClass* pClass) const;
// Overridables
virtual void Serialize(CArchive& ar);//加注:用户序列化需要实现的主要函数
// 诊断函数
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
// Implementation
public:
//与动态创建对象相关的函数
static const AFX_DATA CRuntimeClass classCObject;//加注:CObject的核心功能尽在于此
#ifdef _AFXDLL
static CRuntimeClass* PASCAL _GetBaseClass();//加注:调用基类的CRuntimeClass类型对象,CObject作为基类也搞一个_GetBaseClass函数是为了配合一些函数的功能(例如 IsKindOf,后面我们会看到),派生类如果实现了_DECLARE_DYNAMIC宏,也会为派生类定义一个static 的GetBaseClass函数!
#endif
};
由上可以看出,CObject定义了一个CRuntimeClass类型的静态成员变量:
CRuntimeClass classCObject
还定义了几组函数:
1)构造函数析构函数类,
2)诊断函数,
3)与运行时类信息相关的函数,
4)与串行化相关的函数。
其中:
·一个静态函数:_GetBaseClass;
·五个虚函数:
析构函数、GetRuntimeClass、Serialize、AssertValid、Dump
这些虚拟函数,在CObject的派生类中应该有更具体的实现。必要的话,派生类实现它们时可能要求先调用基类的实现,例如Serialize和Dump就要求这样。
静态成员变量classCObject和相关函数实现了对CObjet特性的支持。
对运行时类信息的支持
该特性用于在运行时确定一个对象是否属于一特定类(是该类的实例),或者从一个特定类派生来的。CObject提供IsKindOf函数来实现这个功能。——用来判断类的继承派生关系
从CObject派生的类要具有这样的特性,需要:
1)定义该类时,在类说明中使用DECLARE_DYNAMIC(CLASSNMAE)宏;
2)在类的实现文件中使用IMPLEMENT_DYNAMIC(CLASSNAME,BASECLASS)宏。
加注:
声明文件:
class CMyView : public CFormView
{
protected: // create from serialization only
CMyView();
DECLARE_DYNCREATE(CMyView)
public:
......
定义文件:
......
IMPLEMENT_DYNCREATE(CMyView, CFormView)
......
(至于这里为什么是DYNCREATE而不是DYNAMIC,因为DECLARE_DYNCREATE定义的同时也定义了DECLARE_DYNAMIC,后面也将讲到)
对动态创建的支持
前面提到了动态创建的概念,就是运行时创建指定类的实例,在MFC中大量使用
从CObject派生的类要具有动态创建的功能,需要:
1)定义该类时,在类说明中使用DECLARE_DYNCREATE(CLASSNMAE)宏;
2)定义一个不带参数的构造函数(默认构造函数); //加注:动态创建内部是调用new ClassName,所以必须满足这条
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
3)在类的实现文件中使用IMPLEMENT_DYNCREATE(CLASSNAME,BASECLASS)宏;
4)使用时先通过宏RUNTIME_CLASS得到类的RunTime信息,然后使用CRuntimeClass的成员函数CreateObject创建一个该类的实例。 加注:用户声明DECLARE_DYNCREATE宏的目的就是为了保证这两个步骤都成功!
例如:
CRuntimeClass* pRuntimeClass = RUNTIME_CLASS(CNname)
//CName必须有一个缺省构造函数
CObject* pObject = pRuntimeClass->CreateObject();
//用IsKindOf检测是否是CName类的实例
Assert( pObject->IsKindOf(RUNTIME_CLASS(CName));
(加注:这几句代码贯穿着CObject的运行时类型识别和动态创建)
对序列化的支持
“序列化”就是把对象内容存入一个文件或从一个文件中读取对象内容的过程。从CObject派生的类要具有序列化的功能,需要:
1)定义该类时,在类说明中使用DECLARE_SERIAL(CLASSNMAE)宏;
2)定义一个不带参数的构造函数(默认构造函数); //加注:序列化中operator>>的实现也用到了动态创建(后面将会有剖析)
3)在类的实现文件中使用IMPLEMENT_SERIAL(CLASSNAME,BASECLASS)宏;
4)覆盖Serialize成员函数
//加注:对于第四条,覆盖Serialize的简单原因是当我们执行ar<<obj的时候其实是调用obj的Serialize的成员函数
(如果直接调用Serialize函数进行序列化读写,可以省略前面三步)
//加注:对于上面一句话对的解析,试想当我们从文件中“读取”一个对象的时候(使用operator >>而不是obj.Serialize()的形式),我们其实是做了什么呢?
1)读取了文件中关于当前类的类型信息(主要是类名)
2)使用该类型信息创建了一个对象(一般就是new ClassNane的操作)
3)将保存在文件中对象的数据赋值给这个新创建的对象
4)返回创建的这个临时对象
因此,当我们使用operator >>从文件“组装”一个对象的时候,就必须实现DECLARE_DYNAMIC、DECLARE_DYNCREATE、DECLARE_SERIAL三个宏
然而当我们使用obj.Serialize()形式读取对象的时候,就不会利用文件中对的运行时类信息进行动态创建了(要不obj是何物),而是通过校验文件中的运行时类信息,直接复制文件中对象的数据到obj
对运行时类信息的支持、动态创建的支持、串行化的支持层(不包括直接调用Serailize实现序列化),这三种功能的层次依次升高。如果对后面的功能支持,必定对前面的功能支持。支持动态创建的话,必定支持运行时类信息;支持序列化,必定支持前面的两个功能,因为它们的声明和实现都是后者包含前者
综合示例:
定义一个支持串行化的类CPerson:
class CPerson : public CObject
{
public:
DECLARE_SERIAL( CPerson )
// 缺省构造函数
CPerson(){};
CString m_name;
WORD m_number;
void Serialize( CArchive& archive );
// rest of class declaration
};
实现该类的成员函数Serialize,覆盖CObject的该函数:
void CPerson::Serialize( CArchive& archive )
{
// 先调用基类函数的实现
CObject::Serialize( archive );
if( archive.IsStoring() )
archive << m_name << m_number;
else
archive >> m_name >> m_number;
}
使用运行时类信息:
CPerson a;
ASSERT( a.IsKindOf( RUNTIME_CLASS( CPerson ) ) );
ASSERT( a.IsKindOf( RUNTIME_CLASS( CObject ) ) );
动态创建:
CRuntimeClass* pRuntimeClass = RUNTIME_CLASS(CPerson)
//Cperson有一个缺省构造函数
CObject* pObject = pRuntimeClass->CreateObject();
Assert( pObject->IsKindOf(RUNTIME_CLASS(CPerson));
加注:关于CArchive的剖析,请看我的另一篇文章!
剖析宏
说了那么多,让我们来剖析宏!
实现CObject特性的机制
由上,清楚了CObject的结构,也清楚了从CObject派生新类时程序员使用CObject特性的方法。现在来考察这些方法如何利用CObjet的结构,CObject结构如何支持这些方法。
首先,要揭示DECLARE_DYNAMIC等宏的内容,然后,分析这些宏的作用。
DECLARE_DYNAMIC等宏的定义
MFC提供了DECLARE_DYNAMIC、DECLARE_DYNCREATE、DECLARE_SERIAL声明宏的两种定义,分别用于静态链接到MFC DLL和动态链接到MFC DLL。对应的实现宏IMPLEMNET_XXXX也有两种定义,但是,这里实现宏就不列举了。
MFC对这些宏的定义如下:
加注:以下是我对相关宏的注解:
//使用MFC DLL
#ifdef _AFXDLL
#define DECLARE_DYNAMIC(class_name) \
protected: \
//_GetBaseClass函数——在使用MFC DLL情况下特有的
static CRuntimeClass* PASCAL _GetBaseClass(); \
public: \
//const的静态数据成员class##class_name,如果class_name为MyClass,那么
//class##class_name就为classMyClass
static const AFX_DATA CRuntimeClass class##class_name; \
//GetRuntimeClass
virtual CRuntimeClass* GetRuntimeClass() const; \//读者是否还记得CObject里有一个virtual GetRuntimeClass,这里是覆写
#define _DECLARE_DYNAMIC(class_name) \
protected: \
static CRuntimeClass* PASCAL _GetBaseClass(); \
public: \
//注意这里class##class_name并没有被定义为const
static AFX_DATA CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const; \
#else
//没有使用MFC DLL——没有_GetBaseClass函数(至于没有_GetBaseClass的原因,这要从CRuntimeClass的CRuntimeClass指针类型的成员m_pBaseClass说起:如果使用了MFC DLL,将使用子类的GetRuntimeClass成员函数来给m_pBaseClass赋值,如果没有使用MFC DLL,将直接给m_pBaseClass赋值)
#define DECLARE_DYNAMIC(class_name) \
public: \
static const AFX_DATA CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const; \
#define _DECLARE_DYNAMIC(class_name) \
public: \
static AFX_DATA CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const; \
#endif
// not serializable, but dynamically constructable
#define DECLARE_DYNCREATE(class_name) \
DECLARE_DYNAMIC(class_name) \
static CObject* PASCAL 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>>(CArchive& ar, class_name* &pOb);
//"为类注册生成静态对象构造函数"
void AFXAPI AfxClassInit(CRuntimeClass* pNewClass);
struct AFX_CLASSINIT
{ AFX_CLASSINIT(CRuntimeClass* pNewClass) { AfxClassInit(pNewClass); } };
struct AFX_CLASSINIT_COMPAT
{ AFX_CLASSINIT_COMPAT(CRuntimeClass* pNewClass); };
//以下是DECLARE_系列宏(定义宏)的实现宏IMPLEMENT_
//使用了MFC DLL的宏的实现
#ifdef _AFXDLL
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) \
//_GetBaseClass的实现——返回基类的运行时信息(基类的CRuntimeClass对象)
//我们追踪RUNTIME_CLASS的实现,得:
//#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
CRuntimeClass* PASCAL class_name::_GetBaseClass() \
{ return RUNTIME_CLASS(base_class_name); } \
//静态成员的初始化——这里很重要,即实现为class_name创建的CRuntimeClass变量
AFX_COMDAT const AFX_DATADEF CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
&class_name::_GetBaseClass, NULL }; \//这是CRuntimeClass类的构造语法
//GetRuntimeClass的实现——返回本身类的运行时信息
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); } \
#define _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) \
CRuntimeClass* PASCAL class_name::_GetBaseClass() \
{ return RUNTIME_CLASS(base_class_name); } \
AFX_COMDAT AFX_DATADEF CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
&class_name::_GetBaseClass, NULL }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); } \
//没使用了MFC DLL的宏的实现
#else
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) \
AFX_COMDAT const AFX_DATADEF CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL }; \//注意这里黑体字和&class_name::_GetBaseClass的不同——因为这是没有使用MFC DLL的情况
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); } \
#define _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) \
AFX_DATADEF CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); } \
#endif
//IMPLEMENT_DYNAMIC
#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
//IMPLEMENT_RUNTIMECLASS的实现在上面
//(IMPLEMENT_RUNTIMECLASS的实现默认就是IMPLEMENT_DYNAMIC的实现)
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)//注意这个NULL,因为只实现IMPLEMENT_DYNAMIC的宏,所以这里参数为NULL(这一位置的参数表示类class_name的动态创建函数的地址)
//IMPLEMENT_DYNCREATE
#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
//属于IMPLEMENT_DYNCREATE的CreateObject函数的实现
CObject* PASCAL class_name::CreateObject() \//所有的动态创建动作都来调用这个函数
{ return new class_name; } \//可见其所为的动态创建是多么简单以及为什么动态创建需要类提供午餐构造了吧,在这里
//实现IMPLEMENT_DYNAMIC
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
class_name::CreateObject)//同时实现支持DYNAMIC的部分
#define IMPLEMENT_SERIAL(class_name, base_class_name, wSchema) \
//实现IMPLEMENT_DYNCREATE
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
//实现IMPLEMENT_DYNAMIC
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, \
class_name::CreateObject) \
//实现IMPLEMENT_SERIAL
/*解析:AFX_CLASSINIT _init_MyClass(RUNTIME_CLASS(MyClass))
AFX_CLASSINIT在前面有声明:
void AFXAPI AfxClassInit(CRuntimeClass* pNewClass);
struct AFX_CLASSINIT
{ AFX_CLASSINIT(CRuntimeClass* pNewClass) { AfxClassInit(pNewClass); } };
struct AFX_CLASSINIT_COMPAT
{ AFX_CLASSINIT_COMPAT(CRuntimeClass* pNewClass); };
定义了一个AFX_CLASSINIlT类型的成员变量,使用子类的运行时类型信息进行构造
*/
AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name)); \
/*解析:CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb)
1,这里重载的operator>>(CArchive& ar, class_name* &pOb) 是应付MyClass>>ar
2,ReadObject内部是调用了对象的Serialize函数(pOb-> Serialize(*this))
3,由此可以看出其宏(DECLARE_SERIAL)的作用:如果用户定义了DECLARE_SERIAL宏,
那么我就给用户实现一个重载的operator >>(相应地给用户以动态创建和运行时信息的支持)
此技术可谓有趣!!!
*/
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb) \
{ pOb = (class_name*) ar.ReadObject(RUNTIME_CLASS(class_name)); \
return ar; } \
(上面列出的这些宏是CObject核心功能实现的核心体现,理解这些宏对理解CObject至关重要)
由于这些声明宏都是在CObect派生类的定义中被使用的,所以从这些宏的上述定义中可以看出,DECLARE_DYNAMIC宏给所在类添加了一个CRuntimeClass类型的静态数据成员class##class_name(类名加前缀class,例如,若类名是CPerson,则该变量名称是classCPerson),且指定为const;两个(使用MFC DLL时,否则,一个)成员函数:虚拟函数GetRuntimeClass和静态函数_GetBaseClass(使用MFC DLL时)。
DECLARE_DYNCREATE宏包含了DECLARE_DYNAMIC,在此基础上,还定义了一个静态成员函数CreateObject。
DECLARE_SERIAL宏则包含了_DECLARE_DYNCREATE,并重载了操作符“>>”(友员函数)。它和前两个宏有所不同的是CRuntimeClass数据成员class##class_name没有被指定为const。
对应地,MFC使用三个宏初始化DECLARE宏所定义的静态变量并实现DECLARE宏所声明的函数:IMPLEMNET_DYNAMIC,IMPLEMNET_DYNCREATE,IMPLEMENT_SERIAL。
首先,这三个宏初始化CRuntimeClass类型的静态成员变量class#class_name。IMPLEMENT_SERIAL不同于其他两个宏,没有指定该变量为const。初始化内容在下节讨论CRuntimeClass时给出。
其次,它实现了DECLARE宏声明的成员函数:
- _GetBaseClass()
返回基类的运行时类信息,即基类的CRuntimeClass类型的静态成员变量。这是静态成员函数。
- GetRuntimeClass()
返回类自己的运行类信息,即其CRuntimeClass类型的静态成员变量。这是虚拟成员函数。
对于动态创建宏,还有一个静态成员函数CreateObject,它使用C++操作符和类的缺省构造函数创建本类的一个动态对象。
- 操作符的重载
对于序列化的实现宏IMPLEMENT_SERIAL,还重载了操作符<<和定义了一个静态成员变量
static const AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name));
比如,对CPerson来说,该变量是_init_Cperson,其目的在于静态成员在应用程序启动之前被初始化,使得AFX_CLASSINIT类的构造函数被调用,从而通过AFX_CLASSINIT类的构造函数在模块状态的CRuntimeClass链表中插入构造函数参数表示的CRuntimeClass类信息。至于模块状态,在后文有详细的讨论。
重载的操作符函数用来在序列化时从文档中读入该类对象的内容,是一个友员函数。定义如下:
CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb)
{
pOb = (class_name*) ar.ReadObject(
RUNTIME_CLASS(class_name));
return ar;
}
回顾CObject的定义,它也有一个CRuntimeClass类型的静态成员变量classCObject,因为它本身也支持三个特性。
以CObject及其派生类的静态成员变量classCObject为基础,IsKindOf和动态创建等函数才可以起到作用。
这个变量为什么能有这样的用处,这就要分析CRuntimeClass类型变量的结构和内容了。下面,在讨论了CRuntimeClass的结构之后,考察该类型的静态变量被不同的宏初始化之后的内容。
接下来让我们了解一下一个重要角色——CRuntimeClass
加注:以下是我对CRuntimeClass的注解:
struct CRuntimeClass
{
// Attributes
//两个记录类的名字、大小
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; //定义了序列化中保存对象到文档的程序的版本
//函数指针1——指向对象的CreateObject静态函数,此函数由DECLARE_DYNCREATE提供
//#define DECLARE_DYNCREATE(class_name) \
//DECLARE_DYNAMIC(class_name) \
//static CObject* PASCAL CreateObject();
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
#ifdef _AFXDLL
//#ifdef _AFXDLL
//这句话的意思是只有使用了MFC DLL的时候,才会有下面这个函数指针变量
//而既然使用了MFC DLL,相应就会有_GetBaseClass函数被提供
//于是下面m_pfnGetBaseClass将被赋值为_GetBaseClass函数的地址
//函数指针2
CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();
#else
CRuntimeClass* m_pBaseClass;//基类的CRuntimeClass变量
#endif
// Operations
CObject* CreateObject();//一个接口,无实际意义,转而调用对象的static CreateObject函数
BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;//IsKindof调用,内部是调用函数指针类型成员变量m_pfnGetBaseClass指向的函数_GetBaseClass以获取基类的CRuntimeClass成员变量,然后通过判断两个CRuntimeClass变量相不相同来判断连个对象是佛属于同一个继承体系
(本来想等到分析IsDerivedFrom的时候再插入此图,但是读者看到分析IsDerivedFrom的地方可以回头看此图!)
// Implementation
void Store(CArchive& ar) const;
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
// CRuntimeClass objects linked together in simple list
CRuntimeClass* m_pNextClass; // linked list of registered classes
};
CRuntimeClass成员变量中有两个是函数指针,还有几个用来保存所在CruntimeClass对象所在类的名字、类的大小(字节数)等。
这些成员变量被三个实现宏初始化,例如:
m_pfnCreateObject,将被初始化指向所在类的静态成员函数CreateObject。CreateObject函数在初始化时由实现宏定义,见上文的说明。
m_pfnGetBaseClass,如果定义了_AFXDLL,则该变量将被初始化指向所在类的成员函数_GetBaseClass。_GetBaseClass在声明宏中声明,在初始化时由实现宏定义,见上文的说明。
下面,分析三个宏对CObject及其派生类的CRuntimeClass类型的成员变量class##class_name初始化的情况,然后讨论CRuntimeClass成员函数的实现。
成员变量class##class_name的内容
IMPLEMENT_DYNCREATE等宏将初始化类的CRuntimeClass类型静态成员变量的各个域,表3-1列出了在动态类信息、动态创建、序列化这三个不同层次下对该静态成员变量的初始化情况:
表3-1 静态成员变量class##class_name的初始化
CRuntimeClass成员变量 | 动态类信息 | 动态创建 | 序列化 |
m_lpszClassName | 类名字符串 | 类名字符串 | 类名字符串 |
m_nObjectSize | 类的大小(字节数) | 类的大小(字节数) | 类的大小(字节数) |
m_wShema | 0xFFFF | 0xFFFF | 1、2等,非0 |
m_pfnCreateObject | NULL | 类的成员函数 CreateObject | 类的成员函数 CreateObject |
m_pBaseClass | 基类的CRuntimeClass变量 | 基类的CRuntimeClass变量 | 基类的CRuntimeClass变量 |
m_pfnGetBaseClass | 类的成员函数 _GetBaseClass | 类的成员函数 _GetBaseClass | 类的成员函数 _GetBaseClass |
m_pNextClass | NULL | NULL | NULL |
m_wSchema类型是UINT,定义了序列化中保存对象到文档的程序的版本。如果不要求支持序列化特性,该域为0XFFFF,否则,不能为0。
Cobject类本身的静态成员变量classCObject被初始化为:
{ "CObject", sizeof(CObject), 0xffff, NULL, &CObject::_GetBaseClass, NULL };
对初始化内容解释如下:
{类名字符串是“CObject”,类的大小是sizeof(CObject),不要求支持序列化,不支持动态创建}。
成员函数CreateObject
动态创建对象是通过语句pRuntimeClass->CreateObject完成的,即调用了CRuntimeClass自己的成员函数,CreateObject函数又调用m_pfnCreateObject指向的函数来完成动态创建任务,如下所示:
CObject* CRuntimeClass::CreateObject()
{
if (m_pfnCreateObject == NULL) //判断函数指针是否空
{
TRACE(_T("Error: Trying to create object which is not ")
_T("DECLARE_DYNCREATE \nor DECLARE_SERIAL: %hs.\n"),
m_lpszClassName);
return NULL;
}
//函数指针非空,继续处理
CObject* pObject = NULL;
TRY
{
pObject = (*m_pfnCreateObject)(); //动态创建对象
//读者还记得IMPLEMENT_DYNCREATE宏的实现吗:
//属于IMPLEMENT_DYNCREATE的CreateObject函数的实现
CObject* PASCAL class_name::CreateObject() \
{ return new class_name; } \
}
END_TRY
return pObject;
}
成员函数IsDerivedFrom
该函数用来帮助运行时判定一个类是否派生于另一个类,被CObject的成员函数IsKindOf函数所调用。其实现描述如下:
如果定义了_AFXDLL则,成员函数IsDerivedFrom调用成员函数m_pfnGetBaseClass指向的函数来向上逐层得到基类的CRuntimeClass类型的静态成员变量,直到某个基类的CRuntimeClass类型的静态成员变量和参数指定的CRuntimeClass变量一致或者追寻到最上层为止。
如果没有定义_AFXDLL,则使用成员变量m_pBaseClass基类的CRuntimeClass类型的静态成员变量。
程序如下所示:
加注:为了更清晰地理解下面将要进行的分析,这里先贴出RUNTIME_CLASS宏的实现,具体RUNTIME_CLASS的细节后面会讲到的:
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
另外有一点如果提前说明也许会好一点:同一个类的不同对象实例共享同一份类的静数据态成员(这里指的是static CRuntimeClass成员):
//const的静态数据成员class##class_name,如果class_name为MyClass,那么
//class##class_name就为classMyClass
static const AFX_DATA CRuntimeClass class##class_name; \
BOOL CRuntimeClass::IsDerivedFrom(
const CRuntimeClass* pBaseClass) const
{
ASSERT(this != NULL);
ASSERT(AfxIsValidAddress(this, sizeof(CRuntimeClass), FALSE));
ASSERT(pBaseClass != NULL);
ASSERT(AfxIsValidAddress(pBaseClass, sizeof(CRuntimeClass), FALSE));
// simple SI case
const CRuntimeClass* pClassThis = this;
while (pClassThis != NULL)//从本类开始向上逐个基类搜索
{
if (pClassThis == pBaseClass)//若是参数指定的类信息
return TRUE;
//类信息不符合,继续向基类搜索
#ifdef _AFXDLL
pClassThis = (*pClassThis->m_pfnGetBaseClass)();//如果定义了_AFXDLL就定义了_GetBaseClass
#else
pClassThis = pClassThis->m_pBaseClass;////如果没定义_AFXDLL就没定义_GetBaseClass
#endif
}
return FALSE; // 搜索完毕,没有匹配,返回FALSE。
}
由于CRuntimeClass类型的成员变量是静态成员变量,所以如果两个类的CruntimeClass成员变量相同,必定是同一个类。这就是IsDerivedFrom和IsKindOf的实现基础。
RUNTIME_CLASS宏
RUNTIME_CLASS宏定义如下:
#define RUNTIME_CLASS(class_name) (&class_name::class##class_name)
为了方便地得到每个类(Cobject或其派生类)的CRuntimeClass类型的静态成员变量,MFC定义了这个宏。它返回对类class_name的CRuntimeClass类型成员变量的引用,该成员变量的名称是“class”加上class_name(类的名字)。例如:
RUNTIME_CLASS(CObject)得到对classCObject的引用;
RUNTIME_CLASS(CPerson)得到对class CPerson的引用。
动态类信息、动态创建的原理 (主要分析了动态创建)
MFC对Cobject动态类信息、动态创建的实现原理:
动态类信息、动态创建都建立在给类添加的CRuntimeClass类型的静态成员变量基础上,总结如下。
C++不支持动态创建,但是支持动态对象的创建。动态创建归根到底是创建动态对象,因为从一个类名创建一个该类的实例最终是创建一个以该类为类型的动态对象。其中的关键是从一个类名可以得到创建其动态对象的代码。//加注:这里是用宏来实现,同样可以使用模板来实现(new T)
在一个类没有任何实例之前,怎么可以得到该类的创建动态对象的代码?借助于C++的静态成员数据技术可达到这个目的:1
1)静态成员数据在程序的入口(main或WinMain)之前初始化。因此,在一个静态成员数据里存放有关类型信息、动态创建函数等,需要的时候,得到这个成员数据就可以了。
2)不论一个类创建多少实例,静态成员数据只有一份。所有的类的实例共享一个静态成员数据,要判断一个类是否是一个类的实例,只须确认它是否使用了该类的这个静态数据。
从前两节的讨论知道,DECLARE_CREATE等宏定义了一个这样的静态成员变量:类型是CRuntimeClass,命名约定是“calss”加上类名;IMPLEMENT_CREATE等宏初始化该变量;RUNTIME_CLASS宏用来得到该成员变量。
(动态类信息的原理在分析CRuntimeClass的成员函数IsDerivedFrom时已经作了了解,主要就是获取每个类的CRuntimeClass类型的数据成员)
总结:
1)动态类信息就是获取该类的静态成员变量CRuntimeClass类型的static成员以及基类的CRuntimeClass类型的static成员
2)动态创建就是利用CRuntimeClass类型的static成员变量的信息调用static CreateObject函数
最后聊聊序列化机制
(关于序列化与运行时类型信息、动态创建的关系,前面已有分析)
由上所述可知,一个类要支持实现序列化,使得它的对象可以保存到文档中或者可以从文档中读入到内存中并生成对象,需要使用动态类信息,而且,需要覆盖基类的Serialize虚拟函数来完成其对象的序列化。
仅仅有类的支持是不够的,MFC还提供了一个归档类CArchive来支持简单类型的数据和复杂对象的读写。
CArchive 在文件和内存对象之间充当一个代理者的角色。它负责按一定的顺序和格式把内存对象写到文件中,或者读出来,可以被看作是一个二进制的流。
一个CArchive对象在要序列化的对象和存储媒体(storage medium,可以是一个文件或者一个Socket)之间起了中介作用。它提供了系列方法来完成序列化,不仅能够把int、float等简单类型数据进行序列化,而且能够把复杂的数据如string等进行序列化,更重要的是它能把复杂的对象(包括复合对象)进行序列化。这些方法就是重载的操作符>>和<<。对于简单类型,它针对不同类型直接实现不同的读写操作;对于复杂的对象,其每一个支持序列化的类都重载了操作符>>,从前几节可以清楚地看到这点:IMPLEMENT_SERIAL给所在类重载了操作符>>。至于<<操作,就不必每个序列化类都重载了。
复杂对象的“<<”操作,先搜索本模块状态的CRuntimeClass链表看是否有“<<”第二个参数指定的对象类的运行类信息(搜索过程涉及到模块状态,将在9.5.2节描述),如果有(无,则返回),则先使用这些信息动态的创建对象(这就是是序列化类必须提供动态类信息、支持动态创建的原因),然后对该对象调用Serilize函数从存储媒体读入对象内容。
复杂对象的“>>”操作先把对象类的运行类信息写入存储媒体,然后对该对象调用Serilize函数把对象内容写入存储媒体。
在创建CArchive对象时,必须有一个CFile对象,它代表了存储媒介。通常,程序员不必做这个工作,打开或保存文档时MFC将自动的创建CFile对象和CArchive对象并在适当的时候调用序列化类的Serialize函数。在后面讨论打开(5.3.3.2节)或者关闭(6.1节)文档时将会看到这样的流程。
CArchive对象被创建时,需要指定它是用来读还是用来写,即指定序列化操作的方向。Serialize函数适用CArchive的函数IsStoring判定CArchive是用于读出数据还是写入数据。
在解释实现序列化的方法时,曾经提到如果程序员直接调用Serilize函数完成序列化,而不借助CArchive的>>和<<操作,则可以不需要动态类信息和动态创建。从上文的论述可以看出,没有CArchive的>>和<<操作,的确不需要动态类信息和动态创建特性。
(未完待续......)
(续)
动手搭建
前面讲过我们最后会实现MFC的核心机能(序列化永久保存对象、动态创建、运行时类信息识别),接下来我们模仿前面的分析来实现这些机制!
(以下每个头文件开头都有设计需要达到的目的说明!)
MyAfx文件
#ifndef MYAFX
#define MYAFX
#include<string>
#include"MyRuntimeClass.h"
#include"MyArchive.h"
/**宏有三个任务:
1,为某个类增加静态的成员方法:——一级宏
1)获取自己MyRuntimeClass变量的方法和获取基类MyRuntimeClass变量的方法
2)为某个类增加成员变量:MyRuntimeClass类型的成员变量
2,创建静态的CreateObject方法,其实也是为类额外增加了一个CreateObject的方法——二级宏
3,序列化支持,增加一个friend重载函数:——三级宏
*/
//声明宏
#define ZHENG_DECLARE_DYNAMIC(class_name)\
public:\
static MyRuntimeClass m_RuntimeClass##class_name; \
public:\
static MyRuntimeClass & GetBaseRuntimeClass();\
virtual MyRuntimeClass & GetRuntimeClass();
#define ZHENG_DECLARE_DYNCREATE(class_name)\
ZHENG_DECLARE_DYNAMIC(class_name)\
static MyObject * CreateObject();
#define ZHENG_DECLARE_SERIAL(class_name)\
ZHENG_DECLARE_DYNCREATE(class_name)\
friend MyArchive & operator<<(MyArchive & ar, class_name & obj); \
friend MyArchive & operator>>(MyArchive & ar, class_name *& obj);
//实现宏
#define ZHENG_IMPLEMENT_DYNAMIC(class_name,base_name)\
MyRuntimeClass class_name::m_RuntimeClass##class_name ={\
#class_name, sizeof(class_name), &class_name::GetBaseRuntimeClass, nullptr}; \
MyRuntimeClass & class_name::GetBaseRuntimeClass(){\
return m_RuntimeClass##base_name; \
}\
MyRuntimeClass & class_name::GetRuntimeClass(){\
return m_RuntimeClass##class_name;\
}
#define ZHENG_IMPLEMENT_DYNCREATE(class_name,base_name)\
MyRuntimeClass class_name::m_RuntimeClass##class_name = { \
#class_name, sizeof(class_name), &class_name::GetBaseRuntimeClass, &class_name::CreateObject/*注意这里*/ }; \
MyRuntimeClass & class_name::GetBaseRuntimeClass(){\
return m_RuntimeClass##base_name; \
}\
MyRuntimeClass & class_name::GetRuntimeClass(){\
return m_RuntimeClass##class_name; \
}\
MyObject * class_name::CreateObject(){\
return new class_name;\
}
#define ZHENG_IMPLEMENT_SERIAL(class_name,base_name)\
ZHENG_IMPLEMENT_DYNCREATE(class_name, base_name)\
MyArchive & operator<<(MyArchive & ar, class_name & obj){\
obj.Serialize(ar); \
return ar;}\
MyArchive & operator>>(MyArchive & ar, class_name *& obj){\
obj = (class_name *)class_name::CreateObject(); \
obj->Serialize(ar); \
return ar; }
#endif
MyApplication头文件和实现文件
#ifndef MYAPPLICATION
#define MYAPPLICATION
#include"MyArchive.h"
#include"MyDocument.h"
/**
1,应用程序对象
2,内部管理着一个文档指针,此文档指针在程序启动的时候会被初始化
*/
class MyApplication{
public:
//关键的构造和析构
MyApplication();
~MyApplication();
MyDocument *GetDocPtr(){
return m_pDefaultDocument;
}
protected:
MyApplication(MyApplication const &);
void operator=(const MyApplication& objectSrc);
private:
static MyDocument * m_pDefaultDocument;
};
#endif
#include"MyApplication.h"
MyDocument * MyApplication::m_pDefaultDocument=nullptr;
MyApplication::MyApplication(){
FILE * fp = fopen(".\\MyMFC.txt", "r");
MyArchive ar(fp,MyArchive::Load);
//序列化读取文件
ar>>m_pDefaultDocument;
//关闭文件
fclose(fp);
}
MyApplication::~MyApplication(){
//清空文件
FILE * fp= fopen(".\\MyMFC.txt", "w");
fclose(fp);//清空
//打开文件——追加
fp = fopen(".\\MyMFC.txt", "a+");
MyArchive ar(fp, MyArchive::Store);
assert(m_pDefaultDocument!=NULL);
//调用序列化写入数据
ar << *m_pDefaultDocument;
//关闭文件
fclose(fp);
}
MyArchive头文件和实现文件
#ifndef MYARCHIVE
#define MYARCHIVE
#include<iostream>
#include<fstream>
#include<string>
#include<memory>//uinque_ptr
using namespace std;
/**
1,通过由宏提供的 CArchive& AFXAPI operator>>(CArchive& ar, class_name* &pOb)
用户对象以ar为参数调用Serialize成员函数
2,在用户的Serialize成员函数内,要通过ar保存/读取基本类型,因此MyArchive内部需要对基本道德数据类型重载ar<</ar>>
3,此软件默认的保存位置,因此需要有默认的文件缓冲区静态成员,程序启动就打开此文件缓冲区
4,读写标志
*/
class MyArchive{
public:
//读写标志
enum Mode{ Load = 0, Store = 1 };
MyArchive(FILE * frome_fp, Mode mod);
//获得内部文件缓冲区指针
FILE * & GetFilePointer(){
return m_pFilePointer;
}
//支持对基本类型输出到文件缓冲区
MyArchive & operator<<(string & frome);
MyArchive & operator<<(int & frome);
//......
MyArchive & operator>>(string & frome);
MyArchive & operator>>(int & frome);
//......
//判断是读/写状态
bool IsLoading(){
return m_boolReadOrWrite == Load ? 1 : 0;
}
bool IsStoring(){
return m_boolReadOrWrite == Store ? 1 : 0;
}
protected:
MyArchive();
MyArchive(MyArchive const &);
void operator=(const MyArchive& objectSrc);
private:
FILE * m_pFilePointer;//文件指针
Mode m_boolReadOrWrite;
};
#endif
#include"MyArchive.h"
MyArchive::MyArchive(FILE * frome_fp, Mode mod){
m_pFilePointer = frome_fp;
m_boolReadOrWrite = mod;//记录模式
}
//写
MyArchive & MyArchive::operator<<(string & frome){
fprintf(m_pFilePointer,"%s",frome.data());
return *this;
}
MyArchive & MyArchive::operator<<(int & frome){
fprintf(m_pFilePointer, "%d", frome);
return *this;
}
//读
MyArchive & MyArchive::operator>>(string & frome){
fscanf(m_pFilePointer, "%s", frome.data());
return *this;
}
MyArchive & MyArchive::operator>>(int & frome){
fscanf(m_pFilePointer, "%d", &frome);
return *this;
}
MyDocument头文件和实现文件
#ifndef MYDOCUMENT
#define MYDOCUMENT
#include<string>
#include <assert.h>
#include"MyAfx.h"
#include"MyArchive.h"
#include"MyObject.h"
using namespace std;
/**
1,一个有着若干数据的文档类
2,实现了Serialize函数
3,支持序列化
*/
class MyDocument:public MyObject{
public:
ZHENG_DECLARE_SERIAL(MyDocument);
MyDocument();
string & GetDocuName(){
return m_DocuName;
}
int & GetNumber(){
return m_Number;
}
//核心函数Serialize
virtual void Serialize(MyArchive & ar);
protected:
private:
int m_Number;
string m_DocuName;
};
#endif
#include"MyDocument.h"
ZHENG_IMPLEMENT_SERIAL(MyDocument, MyObject);
MyDocument::MyDocument(){
m_DocuName = "默认文档名!";
m_Number = 100;
}
void MyDocument::Serialize(MyArchive & ar){
//调用基类的Serialize函数
MyObject::Serialize(ar);
//判断是写/读
if (ar.IsStoring()){//写
ar << m_Number;
ar << m_DocuName;
}
else if (ar.IsLoading()){//读
ar >> m_Number;
ar >> m_DocuName;
}
else
assert(0);//读写状态不确定,抛出异常
}
MyFunc文件
#ifndef MYFUNC
#define MYFUNC
#include"MyRuntimeClass.h"
#include"MyObject.h"
#include <assert.h>
#define MY_RUNTIME_CLASS(class_name) class_name::m_RuntimeClass##class_name
template<typename ObjType>
bool Is_Kind_Of(ObjType & obj, MyRuntimeClass & dest_runtime){
//保证用来测试的类都是经过DECLARE_DYNAMIC的
string str(typeid(obj).name());
assert(obj.GetRuntimeClass().GetObjName() == str.erase(0, str.find(' ') + 1));
//先获取来源对象的MyRuntimeClass对象
bool bFind = false;
MyRuntimeClass * pRuntimePtr = &(obj.GetRuntimeClass());
MyRuntimeClass * pFromt = pRuntimePtr;//判断条件记录上一次
cout << "----欲验证的源对象类型为:" << obj.GetRuntimeClass().GetObjName() << "!(下面为此类型的向上继承体系结构)欲验证归属于:" << dest_runtime.GetObjName() << ":----" << endl;
do{
pFromt = pRuntimePtr;
cout << (*pRuntimePtr).GetObjName() << endl;
if (*pRuntimePtr == dest_runtime){
bFind = true;//记录找到了
break;
}
//通过MyRuntimeClass来循环获取基类的MyRuntimeClass对象
pRuntimePtr = &(pRuntimePtr->GetBaseRuntimeClass());
} while (pFromt->GetObjName() != "MyObject");//获取到MyObject了还不是的话就终止循环
cout << endl;
return bFind;
}
template<typename ObjType1, typename ObjType2>
bool Is_Kind_Of(ObjType1 & obj1, ObjType2 & obj2){
//保证用来测试的类都是经过DECLARE_DYNAMIC的
string str(typeid(obj1).name());
assert(obj1.GetRuntimeClass().GetObjName() == str.erase(0, str.find(' ') + 1));
string str2(typeid(obj2).name());
assert(obj2.GetRuntimeClass().GetObjName() == str2.erase(0, str2.find(' ') + 1));
bool bFind = false;
MyRuntimeClass * pRuntimePtr = &(obj1.GetRuntimeClass());
MyRuntimeClass * pFromt = pRuntimePtr;//判断条件记录上一次
cout << "----欲验证的源对象类型为:" << obj1.GetRuntimeClass().GetObjName() << "!(下面为此类型的向上继承体系结构)欲验证归属于:" << obj2.GetRuntimeClass().GetObjName() << ":----" << endl;
do{
pFromt = pRuntimePtr;
cout << (*pRuntimePtr).GetObjName() << endl;
if (*pRuntimePtr == obj2.GetRuntimeClass()){
bFind = true;//记录找到了
break;
}
//通过MyRuntimeClass来循环获取基类的MyRuntimeClass对象
pRuntimePtr = &(pRuntimePtr->GetBaseRuntimeClass());
} while (pFromt->GetObjName() != "MyObject");//获取到MyObject了还不是的话就终止循环
cout << endl;
return bFind;
}
#endif
MyObject头文件和实现文件
#ifndef MYOBJECT
#define MYOBJECT
#include"MyRuntimeClass.h"
#include"MyArchive.h"
using namespace std;
/**MyObject需要实现的功能:
1,运行时类型识别
2,动态创建
3,序列化
*/
class MyObject{
public:
MyObject(){}
//基类的MyRuntimeClass变量
static MyRuntimeClass m_RuntimeClassMyObject;
public:
//添加根类对的获取自己和根类的MyRuntimeClass的方法
static MyRuntimeClass & GetBaseRuntimeClass();
virtual MyRuntimeClass & GetRuntimeClass();
//序列支持函数
virtual void Serialize(MyArchive & ar);
protected:
};
#endif
#include"MyObject.h"
#include<iostream>
using namespace std;
MyRuntimeClass & MyObject::GetBaseRuntimeClass(){
return m_RuntimeClassMyObject;
}
MyRuntimeClass & MyObject::GetRuntimeClass(){
return m_RuntimeClassMyObject;
}
//静态成员的初始化
MyRuntimeClass MyObject::m_RuntimeClassMyObject = { "MyObject", sizeof(MyObject), &MyObject::GetBaseRuntimeClass,nullptr/*根类MyObject不能1动态创建*/ };
//序列函数
void MyObject::Serialize(MyArchive & ar){
}
MyRuntimeClass头文件和实现文件
#ifndef MYRUNTIMECLASS
#define MYRUNTIMECLASS
#include<crtdefs.h>//size_t
#include<string>
class MyObject;
using namespace std;
/**MyRuntimeClass需要具备对的功能
1,所接管类的类名:用来动态创建和运行时类型识别
2,所接管类的大小
3,动态创建的函数:调用类的静态CreateObject方法
4,获取基类MyRuntimeClass变量的方法:用来判定某个对象是否处于某个继承体系
*/
class MyRuntimeClass{
public:
//构造函数
MyRuntimeClass(string name, size_t size, MyRuntimeClass & (* pGetBaseRuntimeClass)(), MyObject *(* pCreateObject)())
:m_ObjName(name), m_ObjSize(size), m_pGetBaseRuntimeClass(pGetBaseRuntimeClass), m_pCreateObject(pCreateObject){}
//动态创建的函数:
MyObject * CreateObject();
//获取基类MyRuntimeClass成员变量的函数
//调用对象的GetBaseRuntimeClass方法,对象的GetBaseRuntimeClass方法其实是获取他基类的MyRuntimeClass成员变量
MyRuntimeClass & GetBaseRuntimeClass();
//比较函数
bool operator==(MyRuntimeClass & another_obj);
//获取私有变量
string & GetObjName();
size_t & GetObjSize();
protected:
private:
//两个函数指针指向对象的静态函数
MyRuntimeClass & (*m_pGetBaseRuntimeClass)();
MyObject *(* m_pCreateObject)();
string m_ObjName;
size_t m_ObjSize;
};
#endif
#include"MyRuntimeClass.h"
bool MyRuntimeClass::operator == (MyRuntimeClass & another_obj){
return m_ObjName == another_obj.GetObjName() && m_ObjSize == another_obj.GetObjSize();
}
MyObject * MyRuntimeClass::CreateObject(){
return (*m_pCreateObject)();
}
//获取基类MyRuntimeClass成员变量的函数
//调用对象的GetBaseRuntimeClass方法,对象的GetBaseRuntimeClass方法其实是获取他基类的MyRuntimeClass成员变量
MyRuntimeClass & MyRuntimeClass::GetBaseRuntimeClass(){
return (*m_pGetBaseRuntimeClass)();
}
string & MyRuntimeClass::GetObjName(){
return m_ObjName;
}
size_t & MyRuntimeClass::GetObjSize(){
return m_ObjSize;
}
测试
#include<iostream>
using namespace std;
#include"MyAfx.h"
#include"MyObject.h"
#include"MyFunc.h"
#include"MyApplication.h"
#include<sstream>
MyApplication theApp;//全局对象
//用于运行时类信息测试
class MyClass1:public MyObject{
//声明宏
ZHENG_DECLARE_DYNAMIC(MyClass1);
};
//实现宏
ZHENG_IMPLEMENT_DYNAMIC(MyClass1, MyObject);
//测试多重继承
class MyClass2 :public MyClass1{
//声明宏
ZHENG_DECLARE_DYNAMIC(MyClass2);
};
//实现宏
ZHENG_IMPLEMENT_DYNAMIC(MyClass2, MyClass1);
class MyClass3 :public MyClass1{};
class MyClass4{};
//用于动态创建测试
class MyClassCreate :public MyObject{
//声明宏
ZHENG_DECLARE_DYNCREATE(MyClassCreate);
public:
MyClassCreate():str("默认构造必须有!"), a(0){}
public://一般为private,这里为了方便
string str;
int a;
};
//实现宏
ZHENG_IMPLEMENT_DYNCREATE(MyClassCreate, MyObject);
//测试系列化的类
class MyClassBook:public MyObject{
public:
ZHENG_DECLARE_SERIAL(MyClassBook);
MyClassBook();
virtual void Serialize(MyArchive &ar);
string & GetBookName(){return m_Name;}
string & GetBookUser(){return m_User;}
int & GetPrice(){return m_Price;}
private:
int m_Price;
string m_Name;
string m_User;
};
ZHENG_IMPLEMENT_SERIAL(MyClassBook, MyObject);
MyClassBook::MyClassBook(){
m_Price = 100;
m_Name = "[活着]";
m_User = "江海";
}
void MyClassBook::Serialize(MyArchive &ar){
//调用基类的Serialize函数
MyObject::Serialize(ar);
//判断是写/读
if (ar.IsStoring()){//写
ar << m_Price;
ar << m_Name;
ar << m_User;
}
else if (ar.IsLoading()){//读
ar >> m_Price;
ar >> m_Name;
ar >> m_User;
}
else
assert(0);//读写状态不确定,抛出异常
}
void test1(){
cout << "-------------------------------------------" << endl;
cout << "《利用序列化保存对单文档的修改》:" << endl;
cout << "-------------------------------------------" << endl << endl;
cout << theApp.GetDocPtr()->GetDocuName() << endl;
cout << theApp.GetDocPtr()->GetNumber() << endl;
theApp.GetDocPtr()->GetDocuName() = "文档信息修改";//修改文档数据
MyClassBook book;
book.GetPrice() = 39;
//将测试类序列化写到文件
FILE * fp = fopen(".\\test.txt", "w");
fclose(fp);//清空文件
fp = fopen(".\\test.txt", "a+");
MyArchive ar(fp, MyArchive::Store);
ar << book;//序列化读取文件
fclose(fp);
//将测试类序列化读到文件
MyClassBook * pbook = nullptr;
FILE * fp2 = fopen(".\\test.txt", "r");
MyArchive ar2(fp2, MyArchive::Load);
ar2 >> pbook;
fclose(fp2);
cout << endl;
cout << "-------------------------------------------" << endl;
cout << "《序列化我自定义的对象到文件》:" << endl;
cout << "-------------------------------------------" << endl << endl;
cout << "利用序列化从文件动态创建一个Book对象:" << endl;
cout << endl;
cout << pbook->GetBookName() << endl;
cout << pbook->GetBookUser() << endl;
cout << pbook->GetPrice() << endl;
cout << endl;
}
void test2(){
MyObject obj;
MyClass1 mobj1;
MyClass2 mobj2;
MyClass3 mobj3;
MyClass4 mobj4;
//对运行时信息的识别测试:
cout << "-------------------------------------------" << endl;
cout << "《运行是类信息的支持》:" << endl;
cout << "-------------------------------------------" << endl << endl;
//1:普通形式
cout << "1:普通形式" << endl;
Is_Kind_Of(mobj2, MY_RUNTIME_CLASS(MyObject)) ?
cout << "归属于!" << endl << endl : cout << "不归属于!" << endl << endl;
Is_Kind_Of(obj, MY_RUNTIME_CLASS(MyClass1)) ?
cout << "归属于!" << endl << endl : cout << "不归属于!" << endl << endl;
//2:判断第一个对象的类型是否继承于第二个对象的类型
cout << "2:判断第一个对象的类型是否归属于第二个对象的类型的形式" << endl;
Is_Kind_Of(mobj2, obj) ?
cout << "归属于!" << endl << endl : cout << "不归属于!" << endl << endl;
//错误预防1:当用来判断的类型不是继承于MyObject的时候,编译器就会报告错误
//Is_Kind_Of(mobj4, obj) ?
// cout << "归属于!" << endl : cout << "不归属于!" << endl;
//Is_Kind_Of(obj4, MY_RUNTIME_CLASS(MyClass)) ?
// cout << "归属于!" << endl : cout << "不归属于!" << endl;
//错误预防2:当用来判断的类没有声明ZHENG_DECLARE_DYNAMIC宏的时候,将抛出异常
//Is_Kind_Of(mobj3, MY_RUNTIME_CLASS(MyClass)) ?
// cout << "归属于!" << endl : cout << "不归属于!" << endl;
//Is_Kind_Of(mobj3, mobj3) ?
// cout << "归属于!" << endl : cout << "不归属于!" << endl;
}
void test3(){
cout << "-------------------------------------------" << endl;
cout << "《动态创建一个对象》:" << endl;
cout << "-------------------------------------------" << endl << endl;
MyObject * pObj = MY_RUNTIME_CLASS(MyClassCreate).CreateObject();
MyClassCreate * pMyObj = (MyClassCreate *)pObj;
Is_Kind_Of(*pObj, MY_RUNTIME_CLASS(MyClassCreate)) ?
cout << "归属于!" << endl << endl : cout << "不归属于!" << endl << endl;
cout << endl << endl;
}
void main(){
test1();
test2();
test3();
}
输出结果:
-------------------------------------------
《利用序列化保存对单文档的修改》:
-------------------------------------------
文档信息修改
100
-------------------------------------------
《序列化我自定义的对象到文件》:
-------------------------------------------
利用序列化从文件动态创建一个Book对象:
[活着]
江海
39
-------------------------------------------
《运行是类信息的支持》:
-------------------------------------------
1:普通形式
----欲验证的源对象类型为:MyClass2!(下面为此类型的向上继承体系结构)欲验证归属于:MyObject:----
MyClass2
MyClass1
MyObject
归属于!
----欲验证的源对象类型为:MyObject!(下面为此类型的向上继承体系结构)欲验证归属于:MyClass1:----
MyObject
不归属于!
2:判断第一个对象的类型是否归属于第二个对象的类型的形式
----欲验证的源对象类型为:MyClass2!(下面为此类型的向上继承体系结构)欲验证归属于:MyObject:----
MyClass2
MyClass1
MyObject
归属于!
-------------------------------------------
《动态创建一个对象》:
-------------------------------------------
----欲验证的源对象类型为:MyClassCreate!(下面为此类型的向上继承体系结构)欲验证归属于:MyClassCreate:----
MyClassCreate
归属于!
至此,低配版MFC三大核心功能算是完成了,因为CArchive使用了自建的缓冲区,我们设计的MyArchive并没有这种技术,而是简单地利用fprintf、fscanf实现格式的控制,上面文件中MyApplication、MyArchive、MyRuntimeClass、MyObject、MyDocument、MyAfx都与MFC相应
虽然本实现不能媲美于MFC本身(那基本很难),但是我们搭建的框架是完全可以使用的,因此,如果你想具有运行时类信息识别、动态创建、序列化功能的一个框架,那么我想我们可以自己搭建一个了!而且这只是一个雏形,如果你愿意,你可以尽可能地完善它使其更接近MFC本身!