RTTI(执行期类型识别)
在前面章节中我们介绍过Visual C++4.0支持RTTI,重点不外乎是:
1、编译时需选用/GR(/GR的意思是enable C++ RTTI)
2、包含typeinfo.h
3、使用新的typeid运算符。
其实,MFC在编译器支持RTTI之前,就有了这项能力。我们现在要以相同的手法,在Console程序中仿真出来。我希望我的类库具备IsKindOf的能力,能在执行期侦测某个对象是否“属于某种类”,并传回TRUE或FALSE。以前一章的Shape为例,我希望:
CSquare* pSquare = new CSquare;
cout << pSquare->IsKindOf(CSquare); //应该获得1(TRUE)
cout << pSquare->IsKindOf(CRect); //应该获得1(TRUE)
cout << pSquare->IsKindOf(CShape); //应该获得1(TRUE)
cout << pSquare->IsKindOf(CCircle); //应该获得0(FALSE)
以MFC的类层次来说,我希望:
CMyDoc* pMyDoc = new CMyDoc;
cout << pMyDoc->IsKindOf(CMyDoc); //应该获得1(TRUE)
cout << pMyDoc->IsKindOf(CDocument); //应该获得1(TRUE)
cout << pMyDoc->IsKindOf(CCmdTarget); //应该获得1(TRUE)
cout << pMyDoc->IsKindOf(CWnd); //应该获得0(FALSE)
类别型录网与CRuntimeClass
怎么设计RTTI呢?让我们想想,当你看到一种颜色,想知道它的RGB成分比,不查色表行吗?当你持有一种产品,想知道它的型号,不查型录行吗?同样的道理,要达到RTTI的能力,类库的设计者一定要在建构起来的时候,记录必要的信息,以建立型录。型录中的类信息,最好以链表方式链接起来,将来方便一一比较。
我们这份“类别型录”的链表元素将以CRuntimeClass描述,这是一个结构,其中至少需要类名称、链表的Next指针,以及链表的First指针。由于First指针属于全局变量,所以它应该以static修饰之。除此之外,你所看到的其它CRuntimeClass成员都是为了其他目的而准备的,陆陆续续我会介绍。
//in MFC.H
struct CRuntimeClass
{
//属性
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema;
CObject* (PASCAL* m_pfnCreateObject)();
CRuntimeClass* m_pBaseClass;
//CRuntimeClass对象再简单的列表中链接在一起
static CRuntimeClass* pFirstClass; //类列表的开始
CRuntimeClass* m_pNextClass; //链表中登记的类
};
为了能够查找每一个构建的类的类型信息,我们希望每一个类都能拥有一个这样的CRuntimeClass成员变量,并且最好有一定的命名规则,然后,经由某种手段将整个类库建构好之后,“类别型录”能呈现类似这样的风貌:
总结一下上面想要讲的内容:我们希望查询每一个类的类型信息,那么就得有一个包含所有类型信息这样的一个表,每次将类信息与表中信息挨个比对,就能获得想要查的类的类型信息了。那么我们就需要先建立这样一个表。这个表就像上面图中所示的样子。表中的每一项都是一个包含许多条信息的结构。
DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC宏
既然目的是为了创建一个这样的链表,那么第一步就是在每个类中首先建立CRuntimeClass对象,并且声明一个可以抓到该对象地址的函数,我们定义DECLARE_DYNAMIC宏如下:
#define DECLARE_DYNAMIC(class_name) \
public: \
static CRuntimeClass class##class_name
virtual CRuntimeClass* GetRuntimeClass() const;
出现在宏定义中的##,用来告诉编译器,把两个字符串系在一起。如果这么使用宏:
DECLARE_DYNAMIC(CView)
编译器前置处理器实际做出的代码是:
public:
static CRuntimeClass classCView
virtual CRuntimeClass* GetRuntimeClass() const;
这下子,只要在声明类时放入DECLARE_DYNAMIC宏就可以了。
当然,具体每一个CRuntimeClass对象的内容还没有单独指定,以及链接工作也没有做,于是我们再定义IMPLEMENT_DYNAMIC宏:
#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
_IMPLEMENT_RUNTIMECLASS(class_name,base_class_name, 0xFFFF, NULL)
其中,_IMPLEMENT_RUNTIMECLASS又是一个宏。
#define _IMPLEMENT_RUNTIMECLASS(class_name, base_class_name,wSchema,pfnNew) \
static char _lpsz##class_name[] = #class_name; \
CRuntimeClass class_name::class##class_name = { \
_lpsz##class_name, sizeof(class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name, NULL); \
static AFX_CLASSINIT _init_##class_name(&class_name::class##class_name); \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return &class_name :: class##class_name; } \
其中,RUNTIME_CLASS宏定义如下:
#define RUNTIME_CLASS(class_name) \
(&class_name::class##class_name)
看起来整个IMPLEMENT_DYNAMIC内容好像只是指定初值,其实不然,其美妙处在于它所使用的一个struct AFX_CLASSINIT,定义如下:
struct AFX_CLASSINIT
{ AFX_CLASS(CRuntimeClass* pNewClass) ; };
这表示它有一个构造函数(struct和class都有构造函数),定义如下:
AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass* pNewClass)
{
pNewClass->m_pNextClass = CRuntimeClass :: pFirstClass;
CRuntimeClass :: pFirstClass = pNewClass;
}
很显然,此构造函数负责linked list的链接工作。
于是乎,程序中只需要简简单单的两个宏DECLARE_DYNAMIC(Cxxx)和IMPLEMENT_DYNAMIC(Cxxx, Cxxxbase), 就完成了建构数据并加入链表的工作:
可是,链表的头总是需要特别的费心处理,不能套用一般的链表行为方式。我们的类根源CObject,不能套用现成的宏DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC,必须特别设计如下:
//in header file
class CObject
{
public:
virtual CRuntimeClass* GetRuntimeClass() const;
. . .
public:
static CRuntimeClass classCObject;
};
//in implementation file
static char szCObject[] = "CObject";
struct CRuntimeClass CObject::classCObject =
{ szCObject, sizeof(CObject), 0xffff, NULL, NULL};
static AFX_CLASSINIT _init_CObject(&CObject :: classCObject);
CRuntimeClass* CObject::GetRuntimeClass() const
{
return &CObject :: classCObject;
}
并且,CRuntimeClass中的static成员变量应该要初始化
CRuntimeClass* CRuntimeClass::pFirstClass = NULL;
终于,整个“类别型录”链表的头部就这样形成了:
范例程序Frame3在.h文档中有这类声明:
class CObject
{
. . .
};
class CCmdTarget : public CObject
{
DECLARE_DYNAMIC(CCmdTarget)
. . .
};
class CWinThread : public CCmdTarget
{
DECLARE_DYNAMIC(CWinThread)
. . .
};
class CWinApp : public CWinThread
{
DECLARE_DYNAMIC(CWinApp)
. . .
};
class CDocument : public CCmdTarget
{
DECLARE_DYNAMIC(CDocument)
. . .
};
class CWnd : public CCmdTarget
{
DECLARE_DYNAMIC(CWnd) //其实在MFC中是DECLARE_DYNCREATE()
. . .
};
class CFrameWnd : public CWnd
{
DECLARE_DYNAMIC(CFrameWnd) //其实在MFC中是DECLARE_DYNCREATE()
. . .
};
class CView : public CWnd
{
DECLARE_DYNAMIC(CView)
. . .
};
于是组织出这样一张大网:
IsKindOf(类型识别)
有了上图这种“类别型录”网,要实现IsKindOf功能,就再轻松不过了:
1、为CObject加上一个IsKindOf函数,于是此函数将被所有类继承。它将把参数所指定的某个CRuntimeClass对象拿来与类别型录中的元素一一比较。比较成功(在型录中发现),就传回TRUE,否则传回FALSE:
//in header file
class CObject
{
public:
BOOL IsKindOf(const CRuntimeClass* pClass) const;
};
//in implementation file
BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
{
CRuntimeClass* pClassThis = GetRuntimeClass();
while (pClassThis != NULL)
{
if(pClassThis == pClass)
return TRUE;
pClassThis = pClassThis->m_pBaseClass;
}
return FALSE;
}
注意,while循环中所追踪的是“同宗”路线,也就是凭借着m_pBaseClass而非m_pNextClass。假设我们调用的是:CView* pView = new CView;
pView->IsKindOf(RUNTIME_CLASS(CWinApp));
IsKindOf的参数其实就是&CWinApp::classCWinApp。函数利用GetRuntimeClass先取得&CView::classCView,然后循线而上(所谓的循线而上分别指CView、CWnd、CCmdTarget、CObject),每获得一个CRuntimeClass对象指针,就拿来和CView::classCView的指针比较。靠这个土办法,完成了IsKindOf能力。
2、IsKindOf的使用方法如下:
CMyDoc* pMyDoc = new CMyDoc;
CMyView* pMyView = new CMyView;
cout <<pMyDoc->IsKindOf(RUNTIME_CLASS(CMyDoc)); //应该获得TRUE
cout <<pMyDoc->IsKindOf(RUNTIME_CLASS(CDocument)); //应该获得TRUE
cout <<pMyDoc->IsKindOf(RUNTIME_CLASS(CCmdTarget)); //应该获得TRUE
cout <<pMyDoc->IsKindOf(RUNTIME_CLASS(CObject)); //应该获得TRUE
cout <<pMyDoc->IsKindOf(RUNTIME_CLASS(CWinApp)); //应该获得FALSE
cout <<pMyDoc->IsKindOf(RUNTIME_CLASS(CView)); //应该获得FALSE
cout <<pMyView->IsKindOf(RUNTIME_CLASS(CView)); //应该获得TRUE
cout <<pMyView->IsKindOf(RUNTIME_CLASS(CObject)); //应该获得TRUE
cout <<pMyView->IsKindOf(RUNTIME_CLASS(CWnd)); //应该获得TRUE
cout <<pMyView->IsKindOf(RUNTIME_CLASS(CFrameWnd)); //应该获得FALSE