MFC原理第二讲——运行时类识别、类继承关系、类创建20171220
一.类的继承关系
昨天MFC原理第一讲中讲了C**App类,其作用是对程序进行初始化,那么MFC单文档版工程最后生成的窗口中有主窗口、视图、菜单、工具栏等,还有打开文档的功能,对应这些界面会有相应的类来操作,分别是CMainFrame、C**View、C**Doc,菜单和工具栏的操作归于CMainFrame中,**代表用户输入的工程名称。CMainFrame、C**View、C**Doc的类继承关系图如下:
![](https://images2017.cnblogs.com/blog/1291739/201712/1291739-20171220120010803-74298987.png)
在这些类中,CMainFrame负责主框架,C**View负责视图,C**Doc负责文档数据,在C**View类中有个函数C**Doc* GetDocument(),是获取文档类指针。C**View和C**Doc分开是为了降低耦合性,对编译器更新和数据管理都很有帮助。C**Doc也是CCmdTarget的派生类,因为其中也要消息处理,如打开文件的双击操作。对于MFC多文档版工程,会多加一些文档和视图。
有的大游戏的界面设计中会将视图分层,每层有多个视图,顶层覆盖。
二.运行时类识别(CRuntimeClass)
1.运行时类识别实现
程序运行时,平常写的类不会知道它自己的名称和它自己继承自哪个类,当然如果经过特殊处理还是能让类做到识别它自己的名称和它自己继承自哪个类的,这个识别过程就称为运行时类识别。运行时类识别的实现机制是在自己的类中用一个结构体保存自己类的名称和父类的名称,MFC中实现运行时类识别时用到的保存自身信息和创建类对象的结构体CRuntimeClass如下:
struct CRuntimeClass { // Attributes LPCSTR m_lpszClassName; int m_nObjectSize; UINT m_wSchema; // schema number of the loaded class CObject* (__stdcall * m_pfnCreateObject)(); // NULL => abstract class CRuntimeClass* m_pBaseClass; // Operations CObject* CreateObject(); BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const; // CMyRuntimeClass objects linked together in simple list CRuntimeClass* m_pNextClass; // linked list of registered classes };
以上结构体在类中使用时,是定义成静态成员函数的,因为这个结构体是描述类的信息,所有对象共用,并且作为同全局变量生命周期,初始化会很早,通过类域而非类对象和类对象指针就可使用,其中有6个成员变量,1)m_lpszClassName是使用该结构体的类的名称,2)m_nObjectSize是使用该结构体的类的字节大小,3)m_wSchema是版本号,4)CObject* (__stdcall * m_pfnCreateObject)()是一个创建类对象的函数指针,会在静态结构体赋初值时将使用其的类中的一个静态创建对象函数的指针赋给它,该函数指针的存在是为了实现通过类名创建类对象,5)m_pBaseClass是使用该结构体的类的基类中该结构体静态成员指针,6)m_pNextClass编译器没用,一般填NULL,另外还有两个成员函数,1)CObject* CreateObject()的作用是通过使用该结构体的类类名称就创建类使用该结构体的类对象,2)BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const是判断pBaseClass是否是当前使用该结构体的类的父类中的CRuntimeClass成员指针。
2.MFC运行时类识别实例
1)在最初始基类CObject中使用,截取其中一段成员变量和成员函数如下:
//*.h文件 class CObject { public: CObject(); virtual ~CObject(); static CRuntimeClass classCobject; virtual CRuntimeClass* GetRuntimeClass() const; BOOL IsKindOf(const CRuntimeClass* pClass) const; }; //*.cpp文件 CRuntimeClass CObject::classCObject = {"CObject", sizeof(CObject), NULL, NULL, NULL, NULL}; #define MYRUNTIME_CLASS(class_name) ((CMyRuntimeClass*)(&class_name::class##class_name)) CMyRuntimeClass* CMyobject::GetRuntimeClass() const { return RUNTIME_CLASS(CMyobject); } BOOL CMyobject::IsKindOf(const CMyRuntimeClass* pClass) const { CMyRuntimeClass* pClassThis = GetRuntimeClass(); return pClassThis->IsDerivedFrom(pClass); }
试想一个类要识别自己的名称和继承关系是不是都要加上以上代码呢?既然都得加,那么可不可以写成宏,方便书写也方便更改,定义宏如下:
//在该文档中,有的名称前面加了MY或My,其实与没加在本文是一个意思,只是加到程序中都统一 //1) #define MYDECLARE_DYNAMIC(class_name) \ public: \ static const CMyRuntimeClass class##class_name; \ virtual CMyRuntimeClass* GetRuntimeClass() const; \ //2) #define MYDECLARE_DYNCREATE(class_name) \ MYDECLARE_DYNAMIC(class_name) \ static CMyobject* __stdcall CreateObject();\ //给出创建自己类对象的静态成员函数,至于最后创不创建由用户决定 //3) #define MYRUNTIME_CLASS(class_name) ((CMyRuntimeClass*)(&class_name::class##class_name)) //4) #define MYIMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) \ const CMyRuntimeClass class_name::class##class_name = { \ #class_name, sizeof(class class_name), wSchema, pfnNew, \ MYRUNTIME_CLASS(base_class_name), NULL }; \ CMyRuntimeClass* class_name::GetRuntimeClass() const \ { return MYRUNTIME_CLASS(class_name); } //5) #define MYIMPLEMENT_DYNCREATE(class_name, base_class_name) \ CMyobject* __stdcall class_name::CreateObject() \ { return new class_name; } \ MYIMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \ class_name::CreateObject) //6) #define CREATE_OBJECT(class_name)\ (class_name*)MYRUNTIME_CLASS(CMyMainFrame)->CreateObject();
以上六个宏,分别说明如下:1)定义静态CMyRuntimeClass结构体成员变量class##class_name,如classCWnd,和申明返回CMyRuntimeClass指针的虚函数,2)在1)中宏基础上,再定义一个静态创建对象的函数,实现在5)宏中,3)返回class_name类中的CMyRuntimeClass成员指针,4)给静态CMyRuntimeClass结构体成员变量做初始化,并实现成员函数GetRuntimeClass,5)先实现CMyobject* __stdcall class_name::CreateObject(),在其中new自身类对象,并返回,一个静态成员函数可以做到,如果是非静态成员函数,则肯定是不行的,然后加上MYIMPLEMENT_RUNTIMECLASS,6)我自己写的,用于实现通过类名,产生一个类对象指针。
通过类名产生类对象的过程说明:class_name类中定义了静态CMyRuntimeClass结构体成员变量和静态创建对象的函数,静态创建对象的函数作用是new class_name类并且返回,然后将该函数指针赋给CMyRuntimeClass结构体成员变量中的CObject* (__stdcall * m_pfnCreateObject)()成员变量,也许有的人会觉得,这里不是类成员函数吗,指针可以这样赋值吗?其实对于静态成员函数而言,只是加了个类域,只要是同一类域下便可赋值,静态CMyRuntimeClass结构体成员变量和静态创建对象的函数显然是属于同一类域中。最后可以通过CMyRuntimeClass结构体成员变量中的CObject* CreateObject()函数中使用m_pfnCreateObject函数指针创建对象。
技术关键点:1)使用了静态成员函数,2)使用了宏,节省了大量代码的书写,也方便改代码。写完宏后,在每个除CObject外的MFC类的申明中加DECLARE_DYNCREATE(class_name),在实现中加MYIMPLEMENT_DYNCREATE(class_name,base_class_name)即可完成所有MFC类的运行时类识别和继承关系和创建类对象。在MFC中创建新类时,提示是创建一般类还是MFC类的区别就在于要不要继承MFC类并且加宏到申明和实现中。