MFC深入-CObject类
CObject类CObject是大多数MFC类的根类或基类。CObject类有很多有用的特性对运行时类信息的支持对动态创建的支持对串行化的支持对象诊断输出等等。MFC从CObject派生出许多类具备其中的一个或者多个特性。程序员也可以从CObject类派生出自己的类利用CObject类的这些特性。本章将讨论MFC如何设计CObject类的这些特性。首先考察CObject类的定义分析其结构和方法成员变量和成员函数对CObject特性的支持。然后讨论CObject特性及其实现机制。CObject的结构以下是CObject类的定义class CObject{public://与动态创建相关的函数virtual CRuntimeClass* GetRuntimeClass() const;析构函数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 lpszFileName, int nLine);#endif//缺省情况下复制构造函数和赋值构造函数是不可用的//如果程序员通过传值或者赋值来传递对象将得到一个编译错误protected://缺省构造函数CObject();private://复制构造函数私有CObject(const CObject objectSrc); // no implementation//赋值构造函数私有void operator(const CObject objectSrc); // no implementation// Attributespublic://与运行时类信息、串行化相关的函数BOOL IsSerializable() const;BOOL IsKindOf(const CRuntimeClass* pClass) const;// Overridablesvirtual void Serialize(CArchive ar);// 诊断函数virtual void AssertValid() const;virtual void Dump(CDumpContext dc) const;// Implementationpublic://与动态创建对象相关的函数static const AFX_DATA CRuntimeClass classCObject;#ifdef _AFXDLLstatic CRuntimeClass* PASCAL _GetBaseClass();#endif};由上可以看出CObject定义了一个CRuntimeClass类型的静态成员变量CRuntimeClass classCObject还定义了几组函数构造函数析构函数类诊断函数与运行时类信息相关的函数与串行化相关的函数。其中一个静态函数_GetBaseClass五个虚拟函数析构函数、GetRuntimeClass、Serialize、AssertValid、Dump。这些虚拟函数在CObject的派生类中应该有更具体的实现。必要的话派生类实现它们时可能要求先调用基类的实现例如Serialize和Dump就要求这样。静态成员变量classCObject和相关函数实现了对CObjet特性的支持。CObject类的特性下面对三种特性分别描述并说明程序员在派生类中支持这些特性的方法。对运行时类信息的支持该特性用于在运行时确定一个对象是否属于一特定类是该类的实例或者从一个特定类派生来的。CObject提供IsKindOf函数来实现这个功能。从CObject派生的类要具有这样的特性需要定义该类时在类说明中使用DECLARE_DYNAMICCLASSNMAE宏在类的实现文件中使用IMPLEMENT_DYNAMIC(CLASSNAMEBASECLASS)宏。对动态创建的支持前面提到了动态创建的概念就是运行时创建指定类的实例。在MFC中大量使用如前所述框架窗口对象、视对象还有文档对象都需要由文档模板类(CDocTemplate)对象来动态的创建。从CObject派生的类要具有动态创建的功能需要定义该类时在类说明中使用DECLARE_DYNCREATECLASSNMAE宏定义一个不带参数的构造函数默认构造函数在类的实现文件中使用IMPLEMENT_DYNCREATECLASSNAMEBASECLASS宏使用时先通过宏RUNTIME_CLASS得到类的RunTime信息然后使用CRuntimeClass的成员函数CreateObject创建一个该类的实例。例如CRuntimeClass* pRuntimeClass RUNTIME_CLASS(CNname)//CName必须有一个缺省构造函数CObject* pObject pRuntimeClass-CreateObject();//用IsKindOf检测是否是CName类的实例Assert( pObject-IsKindOf(RUNTIME_CLASS(CName));对序列化的支持“序列化”就是把对象内容存入一个文件或从一个文件中读取对象内容的过程。从CObject派生的类要具有序列化的功能需要定义该类时在类说明中使用DECLARE_SERIALCLASSNMAE宏定义一个不带参数的构造函数默认构造函数在类的实现文件中使用IMPLEMENT_SERIALCLASSNAMEBASECLASS宏覆盖Serialize成员函数。如果直接调用Serialize函数进行序列化读写可以省略前面三步。对运行时类信息的支持、动态创建的支持、串行化的支持层不包括直接调用Serailize实现序列化这三种功能的层次依次升高。如果对后面的功能支持必定对前面的功能支持。支持动态创建的话必定支持运行时类信息支持序列化必定支持前面的两个功能因为它们的声明和实现都是后者包含前者。综合示例定义一个支持串行化的类CPersonclass 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 );// now do the stuff for our specific classif( archive.IsStoring() )archive m_name m_number;elsearchive 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));实现CObject特性的机制由上清楚了CObject的结构也清楚了从CObject派生新类时程序员使用CObject特性的方法。现在来考察这些方法如何利用CObjet的结构CObject结构如何支持这些方法。首先要揭示DECLARE_DYNAMIC等宏的内容然后分析这些宏的作用。DECLARE_DYNAMIC等宏的定义MFC提供了DECLARE_DYNAMIC、DECLARE_DYNCREATE、DECLARE_SERIAL声明宏的两种定义分别用于静态链接到MFC DLL和动态链接到MFC DLL。对应的实现宏IMPLEMNET_XXXX也有两种定义但是这里实现宏就不列举了。MFC对这些宏的定义如下#ifdef _AFXDLL //动态链接到MFC DLL#define DECLARE_DYNAMIC(class_name) \protected: \static CRuntimeClass* PASCAL _GetBaseClass(); \public: \static const AFX_DATA CRuntimeClass class##class_name; \virtual CRuntimeClass* GetRuntimeClass() const; \#define _DECLARE_DYNAMIC(class_name) \protected: \static CRuntimeClass* PASCAL _GetBaseClass(); \public: \static AFX_DATA CRuntimeClass class##class_name; \virtual CRuntimeClass* GetRuntimeClass() const; \#else#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_SERIAL(class_name) \_DECLARE_DYNCREATE(class_name) \friend CArchive AFXAPI operator(CArchive ar, class_name* pOb);由于这些声明宏都是在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_DYNAMICIMPLEMNET_DYNCREATEIMPLEMENT_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类的结构与功能从上面的讨论可以看出在对CObject特性的支持上CRuntimeClass类起到了关键作用。下面考查它的结构和功能。CRuntimeClass的结构CruntimeClass的结构如下Struct CRuntimeClass{LPCSTR m_lpszClassName;//类的名字int m_nObjectSize;//类的大小UINT m_wSchema;CObject* (PASCAL* m_pfnCreateObject)();//pointer to function, equal to newclass.CreateObject()//after IMPLEMENTCRuntimeClass* (PASCAL* m_pfnGetBaseClass)(