MFC-RTTI

MFC中的RTTI机制
本文介绍了MFC框架中使用的运行时类型信息(RTTI)机制。MFC通过CRuntimeClass结构体保存类的元数据,如类名、对象大小及创建函数等,支持类信息判断、动态创建和序列化等功能。文章还详细解释了如何利用DECLARE_DYNCREATE等宏简化RTTI的使用。

关于RTTI

正如侯杰所说,文档视图是MFC进化为应用程序框架的灵魂,不可否认,这是MFC最为精炒的设计,十多年前数据与表现分离的思想就被应用在这个框架之上。而在文档视图之下,支撑着它的是运行时类型信息(RTTI)

RTTI允许程序在运行时刻获得类乃至普通类型的信息,这是怎么做到的,其实原理很简单,就是事先将这些信息保存为某种数据结构,保存的工作或由编译器帮你做,或由程序自己完成。程序运行的时候通过某种途径取得这些信息,然后根据这些信息来做运行时判断。

Delphi是一种RTTI非常强的原生语言,通过TypeInfo函数取得类型信息的进入点,然后你就可以判断它是整型还是字符串,是类还是其他的什么东西。如果是类,你可以取得类名,Published段的成员和方法地址,甚至你可以动态设置属性的值。看看它的快速开发环境,以及组件机制,都离不开RTTI的支持。JavaC#就更强了,C#的反射机制允许查询一个类的所有字段,方法属性以及事件。RTTI反映了一种语言的动态性。

那么是否RTTI越强就越好呢,不同的应用领域结论会有所不同,RTTI越强表明你要额外存储的信息就越多,对于上层应用来说当然无关紧要,但对于一些底层的开发,空间效率都是必须仔细考虑的,RTTI反而成了一种负担。

C++几乎是一种全能的语言,它要面对的是各种各样的应用,因而在RTTI这个问题上就不得不慎重行事,因此C++的类型信息仅止于类型的判断,当然你可以扩展,但Bjarne建议尽量少用RTTI。事实上C++背负了太多历史包袱,对很多新特性的支持都是举步维艰的,比如垃圾回收,Bjarne说过:“垃圾回收将使C++不适合做许多底层的工作,而这却正是它的一个设计目标如果原来就把垃圾回收作为C++的一个有机组件部分,那么C++早就可能成为死胎了。

回到MFC,它并没有使用C++自带的类型信息,可能是那些类型信息根本不够用,也可能是MFC出来的太早(类型信息在1993年才成为C++的一部分)MFC自己设计了一套RTTI机制,并用几个宏来简化代码的编写。

MFCRTTI

MFC中保存类型信息的是一个叫CRuntimeClass的结构体,声明如下:

struct CRuntimeClass

{

//类名

LPCSTR m_lpszClassName;

//类实例大小

int m_nObjectSize;

//是否支持序列化的标志

UINT m_wSchema;

//创建类实例的函数指针

CObject* (PASCAL* m_pfnCreateObject)();

//指向基类的运行时结构

CRuntimeClass* m_pBaseClass;

//创建类实例

CObject* CreateObject();

//判断是否从某个类继承而来

BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;

//序列化支持

void Store(CArchive& ar) const;

static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);

//组成运行时结构链表

CRuntimeClass* m_pNextClass;

};

一个具备类型信息的类都必须生成这个运行时结构,并将它保存为自己的静态成员。这个结构提供了三个方面的支持:

1. 类的基本信息,比如类名,对象尺寸,继承关系等。

你可以轻易判断两个类实例是否属于同一个类,比较m_lpszClassName,或比较类的RuntimeClass成员指针。

你可以通过IsDerivedFrom判断两个类的继承关系,前提是这两个类都生成了相应的RuntimeClass

2. 动态创建支持,将类的一个静态成员函数保存进m_pBaseClass,而该函数必然要进行New的操作,比如:

CObject* CTest::CreateObject()

{

return new CText;

}

以后我们用pRuntimeClass->CreateObject()来动态创建对象。

3. 序列化支持,这个以后再说。

现在假设有一个CMyMainFrm类需要类型信息,它的声明可能是这样:

class CMyMainFrm : public CFrameWnd

{

public:

//声明一个针对这个类的运行时结构体

static const CRuntimeClass classCMyMainFrm;

//多态支持

virtual CRuntimeClass* GetRuntimeClass() const;

//动态创建支持

static CObject* PASCAL CreateObject();

public:

CMyMainFrm();

};

实现则是这样:

CObject* PASCAL CMyMainFrm::CreateObject()

{

return new CMyMainFrm;

}

const CRuntimeClass CMyMainFrm::classCMyMainFrm = {

"CMyMainFrm",

sizeof(class CMyMainFrm),

0xFFFF,

CMyMainFrm::CreateObject,

(CRuntimeClass*)(&CFrameWnd::classCFrameWnd),

NULL};

CRuntimeClass* CMyMainFrm::GetRuntimeClass() const

{

return (CRuntimeClass*)(&CMyMainFrm::classCMyMainFrm);

}

CMyMainFrm::CMyMainFrm()

{

}

编写这些代码之后,CMyMainFrm便具备了类型信息,以后通过GetRuntimeClass取得运行时结构就可以在运行时做一些事情了。

与消息映射一样, 每一个类都要写这一堆代码实在是麻烦,使用宏简化一下吧,于是有了DECLARE_DYNCREATE IMPLEMENT_DYNCREATE宏;有些类可能只需要基本的类型判断,而不需要动态创建,于是有了DECLARE_DYNAMICIMPLEMENT_DYNAMIC宏;有些类还需要序列化的能力,于是有了DECLARE_SERIALIMPLEMENT_SERIAL

其中SERIAL包括DYNCREATEDYNCREATE包括DYNAMIC,总结起来如下表所示:

说明

DECLARE_DYNAMI IMPLEMENT_DYNAMIC

基本类信息判断

DECLARE_DYNCREATE

IMPLEMENT_DYNCREATE

基本类信息判断

动态创建

DECLARE_SERIAL

IMPLEMENT_SERIAL

基本类信息判断

动态创建

序列化支持

DYNCREATE为例,CMyMainFrm的声明和实现变成下面这样子:

class CMyMainFrm : public CFrameWnd

{

DECLARE_DYNCREATE(CMyMainFrm)

public:

CMyMainFrm();

};

IMPLEMENT_DYNCREATE(CMyMainFrm, CFrameWnd)

CMyMainFrm::CMyMainFrm()

{

}

类型信息使MFC有了一定的动态性,但这种动态性是受到限制的,要达到快速开发的级别仍然很难。不过有它的辅助已经可以完成很多高级的设计工作,比如文档视图,而这将是后面的主题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值