您的位置:首页 > 其它

学习笔记之深入浅出MFC 第9章 仿真MFC之二

2016-05-08 11:36 323 查看
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: