RTTI(Runtime Type Identification,运行时类型识别) 程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。MFC 早在编译器支持 RTTI 之前,就具有了这项能力。承接上一章,我们现在要在 Console 程序中将 RTTI 仿真出来。我希望我的类库具备 IsKindOf() 的能力,能够在执行器检查某个对象是否"属于某种类",并传回 TRUE 或 FALSE。为了更直观地查看结果,我在 IsKindOf() 中加入了输出,使其达到如下的效果:
RTTI能做什么?
//随意写了几个测试用例
CObject cobject;
CCmdTarget ccmdtarget;
CWnd cwnd;
CWinApp cwinapp;
CView cview;
CObject cobject;
CCmdTarget ccmdtarget;
CWnd cwnd;
CWinApp cwinapp;
CView cview;
//RUNTIME_CLASS 会在后文中详解
cobject.IsKindOf(RUNTIME_CLASS(CCmdTarget));
ccmdtarget.IsKindOf(RUNTIME_CLASS(CObject));
cwnd.IsKindOf(RUNTIME_CLASS(CWinThread));
cwinapp.IsKindOf(RUNTIME_CLASS(CObject));
cview.IsKindOf(RUNTIME_CLASS(CCmdTarget));
cobject.IsKindOf(RUNTIME_CLASS(CCmdTarget));
ccmdtarget.IsKindOf(RUNTIME_CLASS(CObject));
cwnd.IsKindOf(RUNTIME_CLASS(CWinThread));
cwinapp.IsKindOf(RUNTIME_CLASS(CObject));
cview.IsKindOf(RUNTIME_CLASS(CCmdTarget));
应该有如下输出:
COBject is not CCmdTarget
CCmdTarget is COBject
CView is CCmdTarget
CWinApp is COBject
CWnd is not CWinThread
COBject is not CCmdTarget
CCmdTarget is COBject
CView is CCmdTarget
CWinApp is COBject
CWnd is not CWinThread
为了达到上述目标,本文将分为四个部分讲解:
1.CRunTimeClass 类
2.类别型录网
3.DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC 宏
4.IsKindOf(类型识别) 实现
1.CRunTimeClass 类
2.类别型录网
3.DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC 宏
4.IsKindOf(类型识别) 实现
相对于上一章博客中的代码,本章只修改了 [afx.h] [afx.cpp] [afxwin.h] [afxwin.cpp] [My.cpp] 5个文件,读者可以手动修改,也可以从文章底部附录一给出的链接下载完整源码。
1.CRunTimeClass 类
如何设计 RTTI 呢?当你拿着一个产品,想要知道它的型号,是不是就得去查它的型号表?同样地,要达到 RTTI 的能力,我们必须给每一个类贴上一个标签(注意不是对象而是类),上面写明了该类是什么,它的基类是什么等等,即我们(类库的设计者) 一定要在类构建起来的时候,记录必要的信息,存放这些信息的结构称为型录,型录最好以链表形式连接成为"类别型录网",将来方便一一比较。
我们这份"类别型录网"的链表元素,将以 CRuntimeClass 描述之,该结构如下所示:
我们这份"类别型录网"的链表元素,将以 CRuntimeClass 描述之,该结构如下所示:
struct CRuntimeClass
{
LPCSTR m_lpszClassName;
int m_ObjectSize;
UINT m_wSchema;
CObject* (PASCAL* m_pfnCreateObject)();
CRuntimeClass* m_pBaseClass;
CRuntimeClass* m_pNextClass;
const AFX_CLASSINIT* m_pClassInit;
};
LPCSTR m_lpszClassName:类名称
m_ObjectSize:对象大小
m_Schema:分类编号(对不可分类的类,该值为-1,即 0xFFFF)
(*m_pfnCreateObject)():是一个指向缺省的构造函数的函数指针,该构造函数创建一个你的类的对象(只有在类支持动态创建时才有效;否则,返回NULL)。
m_pBaseClass:指向基类的指针
m_pNextClass:指向下一个型录表的指针
m_pClassInit:用于整个类别型录网的构建与维护
其中最后一个成员变量为 AFX_CLASSINIT 结构体的指针,该结构体如下,里面只有一个构造函数(注意与C的区别,C++ 结构体可以与类相似),该函数的 pModuleState->m_classList.AddHead(pNewClass) 操作即是在构建这个类别型录网,但这里仿真不需要,就不深究了,我们在代码中将该构造函数写为空函数就行。
m_ObjectSize:对象大小
m_Schema:分类编号(对不可分类的类,该值为-1,即 0xFFFF)
(*m_pfnCreateObject)():是一个指向缺省的构造函数的函数指针,该构造函数创建一个你的类的对象(只有在类支持动态创建时才有效;否则,返回NULL)。
m_pBaseClass:指向基类的指针
m_pNextClass:指向下一个型录表的指针
m_pClassInit:用于整个类别型录网的构建与维护
其中最后一个成员变量为 AFX_CLASSINIT 结构体的指针,该结构体如下,里面只有一个构造函数(注意与C的区别,C++ 结构体可以与类相似),该函数的 pModuleState->m_classList.AddHead(pNewClass) 操作即是在构建这个类别型录网,但这里仿真不需要,就不深究了,我们在代码中将该构造函数写为空函数就行。
struct AFX_CLASSINIT
{ AFX_CLASSINIT(CRuntimeClass* pNewClass) { AfxClassInit(pNewClass); } };
void AFXAPI AfxClassInit(CRuntimeClass* pNewClass);
{
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
pModuleState->m_classList.AddHead(pNewClass);
AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
}
每一个欲实现 RTTI 的类都必须有 CRuntimeClass 结构,因此每一个欲实现 RTTI 的类中,就必须有一个 static CRuntimeClass 变量。对于类中的 CRuntimeClass 结构,我们用 "class"+类名 来命名之,比如 CObject 中的 CRuntimeClass 结构就被命名为: classCObject。然后,经由某种手段将所有 CRuntimeClass 结构组成一张"类别型录网"。
2.类别型录网
所有 CRuntimeClass 组成的"类别型录网"应该长成这样(以该仿真中涉及的类为例):
其中,头结点的创建、链表的维护通通交给了 m_pClassInit 指向的类的构造方法了,稍后会介绍到。
3.DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC 宏
RTTI 的实现其实就是 RuntimeClass 结构组成的链表加上一堆宏实现的,所以接下来将会介绍一堆宏,看起来可能有点吓人,不要怕,不过就是文字替换罢了,跟着我一步一步看就是了。
前面已经说明了,每一个类中都必须声明一个 static RuntimeClass 变量用于类型识别,同时也需要一个成员函数能够获得该类的 RuntimeClass 结构的地址,于是一个 CRuntimeClass* GetRuntimeClass() 函数是必需的,由于每一个类都有,于是我们将其声明为虚函数 virtual。既然我们需要在每一个类中声明这些内容,然后在每一个实现文件中对 RuntimeClass 结构填充,并实现 GetRuntimeClass() 函数,那为什么不用宏来简化这些操作呢?假设我们能用 DECLARE_DYNAMIC 完成声明,能用 IMPLEMENT_DYNAMIC 完成实现,岂不美哉?于是就出现了下面的声明宏定义 DECLARE_DYNAMIC:
前面已经说明了,每一个类中都必须声明一个 static RuntimeClass 变量用于类型识别,同时也需要一个成员函数能够获得该类的 RuntimeClass 结构的地址,于是一个 CRuntimeClass* GetRuntimeClass() 函数是必需的,由于每一个类都有,于是我们将其声明为虚函数 virtual。既然我们需要在每一个类中声明这些内容,然后在每一个实现文件中对 RuntimeClass 结构填充,并实现 GetRuntimeClass() 函数,那为什么不用宏来简化这些操作呢?假设我们能用 DECLARE_DYNAMIC 完成声明,能用 IMPLEMENT_DYNAMIC 完成实现,岂不美哉?于是就出现了下面的声明宏定义 DECLARE_DYNAMIC:
#define DECLARE_DYNAMIC(class_name) \
public: \
static const CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const;
符号"\"表示换行连接,我们也可以去掉所有的"\"并把宏写成一行,代码也是等价的。##表示把宏参数名与宏定义代码序列中的标识符连接在一起,形成一个新的标识符。如果我们这么调用:
DECLARE_DYNAMIC(CObject)
宏替换后的结果为:
DECLARE_DYNAMIC(CObject)
宏替换后的结果为:
public:
static const CRuntimeClass classCObject;
virtual CRuntimeClass* GetRuntimeClass() const;
声明完了,接下来编写实现的宏定义 IMPLEMENT_DYNAMIC:
#define IMPLEMENT_DYNAMIC(class_name,base_class_name) \
IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,0xFFFF,NULL,NULL)
#define IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,wSchema,pfnNew,class_init) \
const CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL, class_init }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); }
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
由于 IMPLEMENT_RUNTIMECLASS 宏在"动态创建"(下一章节)中还会用到,它被单独定位一个宏。RUNTIME_CLASS(class_name) 宏很简单就是返回 class_name 中的 RuntimeClass 结构体的地址。IMPLEMENT_RUNTIMECLASS 宏就是填充 class_name 中的 RuntimeClass 结构体并且覆盖 GetRuntimeClass()方法。假如我们这么调用:
IMPLEMENT_DYNAMIC(CCmdTarget,CObject)
宏替换后的结果为:
IMPLEMENT_DYNAMIC(CCmdTarget,CObject)
宏替换后的结果为:
const CRuntimeClass classCCmdTarget = {
CCmdTarget,sizeof(class CCmdTarget),0xFFFF,NULL,
((CRuntimeClass*)(&CObject::classCObject)),NULL,NULL };
CRuntimeClass* CCmdTarget::GetRuntimeClass() const
{ return ((CRuntimeClass*)(&CCmdTarget::classCCmdTarget)); }
前一段为 CRuntimeClass 结构的填充,后一段为 GetRuntimeClass() 的实现。
4.IsKindOf(类型识别) 实现
当你看到这里,RTTI 的实现其实已经实现了,剩下的就是调用这些宏就是了,很简单,只需要在实现 RTTI 的类的声明中加入 DECLARE_DYNAMIC 宏,在实现文件中加入 IMPLEMENT_DYNAMIC 宏,最后写一个 IsKindOf() 函数就大功告成了。
IsKindOf() 函数很简单,如下所示:
IsKindOf() 函数很简单,如下所示:
BOOL CObject::IsKindOf(const CRuntimeClass* pClass)const
{
CRuntimeClass* pClassThis = GetRuntimeClass();
while(pClassThis != NULL)
{
if(pClassThis == pClass)
{
cout<<GetRuntimeClass()->m_lpszClassName<<" is "<<pClass->m_lpszClassName<<endl;
return TRUE;
}
pClassThis = pClassThis->m_pBaseClass;
}
cout<<GetRuntimeClass()->m_lpszClassName<<" is not "<<pClass->m_lpszClassName<<endl;
return FALSE;
}
若这么调用:
A a;
a.IsKindOf(RUNTIME_CLASS(B));
则会将 A 类以及 A 的父类(通过 m_pBaseClass 指针逐级往上)的 CRuntimeClass 的地址与 B 类的 CRuntimeClass 的地址进行比较并返回比较结果,这里我根据返回结果加入了一句输出方便查看。
相对于上一章节的代码,只改动了 5 个文件,依次如下:
1.afx.h
加入了类型声明:
class CObject;
struct CRuntimeClass;
struct AFX_CLASSINIT;
添加了预定义:
#define FALSE 0;
#define TRUE 1;
#define PASCAL __stdcall
#define AFXAPI __stdcall
typedef char CHAR;
typedef const CHAR* LPCSTR;
typedef int BOOL;
typedef unsigned int UINT;
添加了前 3 部分中的所有内容,所有的宏,所有的数据结构。
由于基类 CObject 没有父类,因此手动添加了 Cobject 类的 GetRuntimeClass()函数声明,CRuntimeClass 结构体声明。还添加了 IsKindOf() 函数。代码如下:
1.afx.h
加入了类型声明:
class CObject;
struct CRuntimeClass;
struct AFX_CLASSINIT;
添加了预定义:
#define FALSE 0;
#define TRUE 1;
#define PASCAL __stdcall
#define AFXAPI __stdcall
typedef char CHAR;
typedef const CHAR* LPCSTR;
typedef int BOOL;
typedef unsigned int UINT;
添加了前 3 部分中的所有内容,所有的宏,所有的数据结构。
由于基类 CObject 没有父类,因此手动添加了 Cobject 类的 GetRuntimeClass()函数声明,CRuntimeClass 结构体声明。还添加了 IsKindOf() 函数。代码如下:
//afx.h
class CObject;
struct CRuntimeClass;
struct AFX_CLASSINIT;
//演示需要,MFC实际上不包含<iostream>
#include <iostream>
using namespace std;
//实际上下面这些重定义写其他头文件中,MFC编程时通常会自动包含该头文件,为演示方便就写这了
#define FALSE 0;
#define TRUE 1;
#define PASCAL __stdcall
#define AFXAPI __stdcall
typedef char CHAR;
typedef const CHAR* LPCSTR;
typedef int BOOL;
typedef unsigned int UINT;
#define DECLARE_DYNAMIC(class_name) \
public: \
static const CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const;
#define IMPLEMENT_DYNAMIC(class_name,base_class_name) \
IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,0xFFFF,NULL,NULL)
#define IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,wSchema,pfnNew,class_init) \
const CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL, class_init }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); }
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
//CObect类声明
class CObject
{
public:
CObject();
virtual CRuntimeClass* GetRuntimeClass() const;
BOOL IsKindOf(const CRuntimeClass* pClass)const;
static CRuntimeClass classCObject;
};
void AFXAPI AfxClassInit(CRuntimeClass* pNewClass);
/*
void AFXAPI AfxClassInit(CRuntimeClass* pNewClass)
{
AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
pModuleState->m_classList.AddHead(pNewClass);
AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
}
*/
struct AFX_CLASSINIT
{ AFX_CLASSINIT(CRuntimeClass* pNewClass) { AfxClassInit(pNewClass); } };
struct CRuntimeClass
{
LPCSTR m_lpszClassName;
int m_ObjectSize;
UINT m_wSchema; //分类编号(对不可分类的类,该值为-1)
CObject* (PASCAL* m_pfnCreateObject)();
CRuntimeClass* m_pBaseClass;
CRuntimeClass* m_pNextClass;
const AFX_CLASSINIT* m_pClassInit;
};
2.afx.cpp
添加了 CObject 新增函数的实现。CObject::CRuntimeClass 的填充。AfxClassInit 的实现(空)。代码如下:
添加了 CObject 新增函数的实现。CObject::CRuntimeClass 的填充。AfxClassInit 的实现(空)。代码如下:
//afx.cpp
#include "afx.h"
//CObject类声明
CObject::CObject()
{
cout<<"CObject Constructor."<<endl;
}
BOOL CObject::IsKindOf(const CRuntimeClass* pClass)const
{
CRuntimeClass* pClassThis = GetRuntimeClass();
while(pClassThis != NULL)
{
if(pClassThis == pClass)
{
cout<<GetRuntimeClass()->m_lpszClassName<<" is "<<pClass->m_lpszClassName<<endl;
return TRUE;
}
pClassThis = pClassThis->m_pBaseClass;
}
cout<<GetRuntimeClass()->m_lpszClassName<<" is not "<<pClass->m_lpszClassName<<endl;
return FALSE;
}
CRuntimeClass* CObject::GetRuntimeClass() const
{
return &CObject::classCObject;
}
struct CRuntimeClass CObject::classCObject={
"COBject",sizeof(CObject),0xFFFF,NULL,NULL,NULL,NULL};
void AFXAPI AfxClassInit(CRuntimeClass* pNewClass)
{
//类别型录网的建立与维护,如完成 m_pNextClass 指针的赋值等。
}
3.afxwin.h
在所有类中加入了 DECLARE_DYNAMIC 宏。代码如下:
在所有类中加入了 DECLARE_DYNAMIC 宏。代码如下:
//afxwin.h
#pragma once
#include "afx.h"
//CCmdTarget类声明
class CCmdTarget : public CObject
{
DECLARE_DYNAMIC(CCmdTarget)
public:
CCmdTarget();
};
//CDocument类声明
class CDocument : public CCmdTarget
{
DECLARE_DYNAMIC(CDocument)
public:
CDocument();
};
//CWnd类声明
class CWnd : public CCmdTarget
{
DECLARE_DYNAMIC(CWnd) //其实在 MFC 中是 DECLARE_DYNCREATE(),见下一章
public:
CWnd();
virtual BOOL Create();
BOOL CreateEx();
virtual BOOL PreCreateWindow();
};
//CFrameWnd类声明
class CFrameWnd : public CWnd
{
DECLARE_DYNAMIC(CFrameWnd) //其实在 MFC 中是 DECLARE_DYNCREATE(),见下一章
public:
CFrameWnd();
};
//CView类声明
class CView : public CWnd
{
DECLARE_DYNAMIC(CView)
public:
CView();
};
//CWinThread类声明
class CWinThread : public CCmdTarget
{
DECLARE_DYNAMIC(CWinThread)
public:
CWnd* m_pMainWnd;
CWinThread();
virtual BOOL InitInstance();
virtual int Run();
};
//CWinApp类声明
class CWinApp : public CWinThread
{
DECLARE_DYNAMIC(CWinApp)
public:
CWinApp();
virtual BOOL InitApplication();
virtual BOOL InitInstance(); //覆盖
virtual int Run(); //覆盖
};
4.afxwin.cpp
加入了所有类的 IMPLEMENT_DYNAMIC 宏。代码如下:
加入了所有类的 IMPLEMENT_DYNAMIC 宏。代码如下:
//afxwin.cpp
#include "afxwin.h"
//这里导入"CMyApp.h"是为了使用 theApp 全局变量以使用改造版的 AfxGetApp()
#include "My.h"
extern CMyApp theApp;
//CCmdTarget类方法定义
IMPLEMENT_DYNAMIC(CCmdTarget,CObject)
CCmdTarget::CCmdTarget()
{
cout<<"CCmdTarget Constructor."<<endl;
}
//CDocument类方法定义
IMPLEMENT_DYNAMIC(CDocument,CCmdTarget)
CDocument::CDocument()
{
cout<<"CDocument Constructor."<<endl;
}
//CWnd类方法定义
IMPLEMENT_DYNAMIC(CWnd,CCmdTarget)
CWnd::CWnd()
{
cout<<"CWnd Constructor."<<endl;
}
BOOL CWnd::Create()
{
cout<<"CWnd::Create()."<<endl;
return TRUE;
}
BOOL CWnd::CreateEx()
{
cout<<"CWnd::CreateEx()."<<endl;
return TRUE;
}
BOOL CWnd::PreCreateWindow()
{
cout<<"CWnd::PreCreateWindow()."<<endl;
return TRUE;
}
//CFrameWnd类方法定义
IMPLEMENT_DYNAMIC(CFrameWnd,CWnd)
CFrameWnd::CFrameWnd()
{
cout<<"CFrameWnd Constructor."<<endl;
}
//CView类方法定义
IMPLEMENT_DYNAMIC(CView,CWnd)
CView::CView()
{
cout<<"CView Constructor."<<endl;
}
//CWinThread类方法定义
IMPLEMENT_DYNAMIC(CWinThread,CCmdTarget)
CWinThread::CWinThread()
{
cout<<"CWinThread Constructor."<<endl;
}
BOOL CWinThread::InitInstance()
{
cout<<"CWinThread::InitInstance()."<<endl;
return TRUE;
}
int CWinThread::Run()
{
cout<<"CWinThread::Run()."<<endl;
return 1;
}
//CWinApp类方法定义
IMPLEMENT_DYNAMIC(CWinApp,CWinThread)
CWinApp::CWinApp()
{
cout<<"CWinApp Constructor."<<endl;
}
BOOL CWinApp::InitInstance()
{
cout<<"CWinApp::InitInstance()."<<endl;
return TRUE;
}
BOOL CWinApp::InitApplication()
{
cout<<"CWinApp::InitApplication()."<<endl;
return TRUE;
}
int CWinApp::Run()
{
cout<<"CWinApp::Run()."<<endl;
return 1;
}
5.My.cpp
在 main() 函数中增加了 IsKindOf() 函数的测试代码。
代码如下:
在 main() 函数中增加了 IsKindOf() 函数的测试代码。
代码如下:
//My.cpp
#include "stdafx.h"
#include "My.h"
#include "MyFrame.h"
#include "MyDoc.h"
#include "MyView.h"
//CMyWinApp类方法定义
CMyApp::CMyApp()
{
cout<<"CMyApp Constructor."<<endl;
}
BOOL CMyApp::InitInstance() //覆盖
{
cout<<"CMyApp::InitInstance()."<<endl;
//下面的注释为 MFC 源码中的内容,使用RTTI实例化了CMyDoc、
//CMyFrame、CMyView,并且使用 CDocTemplate 类来连接管理
/*
// 注册应用程序的文档模板。文档模板
// 将用作文档、框架窗口和视图之间的连接
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMyFrame), // 主 SDI 框架窗口
RUNTIME_CLASS(CMyView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
*/
this->m_pMainWnd = new CMyFrame();
return TRUE;
}
//全局变量
CMyApp theApp;
int main()
{
theApp.InitApplication();
theApp.InitInstance();
theApp.Run();
//随意写了几个测试用例
CObject cobject;
CCmdTarget ccmdtarget;
CWinThread cwinthread;
CWnd cwnd;
CWinApp cwinapp;
CView cview;
CFrameWnd cframewnd;
CDocument cdocument;
cout<<endl;
cobject.IsKindOf(RUNTIME_CLASS(CCmdTarget));
ccmdtarget.IsKindOf(RUNTIME_CLASS(CObject));
cview.IsKindOf(RUNTIME_CLASS(CCmdTarget));
cwinapp.IsKindOf(RUNTIME_CLASS(CObject));
cwnd.IsKindOf(RUNTIME_CLASS(CWinThread));
return 0;
}
程序运行结果如下:
//构造函数的输出已经省略
COBject is not CCmdTarget
CCmdTarget is COBject
CView is CCmdTarget
CWinApp is COBject
CWnd is not CWinThread
//构造函数的输出已经省略
COBject is not CCmdTarget
CCmdTarget is COBject
CView is CCmdTarget
CWinApp is COBject
CWnd is not CWinThread