MFC中支持运行时类型识别与动态创建。这更多的是设计问题。理解了其中的设计,可以更快定位用MFC框架开发的代码。通过研究MFC实现这些的细节,可以更快地掌握其他开发框架的设计思想。
宏定义中的"#"与 “##”
MFC框架大量使用了宏定义来“自动”完成框架的声明与实现工作。因此首先复习下宏中的#与##功能是有必要的。
“#”
单独的一个#符号,作用是“字符串化”,比如:
#define ToString(x) #x
则有:
int main()
{
//相当于printf("%s\r\n", “Hello, world!”);
printf("%s\r\n", ToString(Hello, world!);
}
有些框架中会利用这样的字符串化,形成字符常量到字符串的映射,比如,用这样的方法制作消息响应表:
struct tagMsgMap {
int nMsg;
char *szMsg;
};
#define MSG_ENTRY(Msg) {Msg, #Msg}
tagMsgMap g_MsgMapEntry[]= {
MSG_ENTRY(WM_COMMAND),
MSG_ENTRY(WM_KEYUP),
…
};
“##”
"##"的作用常被解释为“连接符”,其实从其存在目的解释,说成“分隔符”也不算错。因为设计它的目的之一就是用来解决某些情况下不能用空格来分隔宏定义的问题。比如:
#define PRE(name) prefix_##name
以上的例子,若将##换成空格,则prefix_与name之间会有空格,不符合我们期待的“PRE(haha)等价于prefix_haha”。
总结一下:##起到代替空格的分隔作用,并且在宏展开时,会消除自身前面的空格,如:
#define MACRO1(name, type) type name_##type##type
#define MACRO2(name, type) type name####type##_type
/* 等价于: int name_int_type; */
MACRO1(a1, int);
/* 等价于: int a1_int_type; */
MACRO2(a1, int);
因为##分隔的位置不同,导致编译器预处理对TOKEN的识别也不同。并且,因为##为消息自身前面的空格,因此以上定义的宏可以写成以下:
#define MACRO1(name, type) type name_ ##type ##type
#define MACRO2(name, type) type name ## ##type ##_type
设计
MFC的类都有IsKindOf()这个成员函数,用来判断自己是否属于某个类(或其派生类),要达到这个目的很简单,可以将所有的派生关系形成一颗单向树,识别类型时,从节点向根遍历并作判断。
此外,MFC还支持从字符串创建类(这也是序列化的必要条件),这就需要有一个全局的容器,里面装有所有类的创建函数。
为了满足以上两条,MFC设计了CRuntimeClass类来记录支持RTTI及动态创建的类的信息,其简化定义如下:
struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName; //类名称
int m_nObjectSize; //类大小
UINT m_wSchema; // 版本号
CObject* (PASCAL* m_pfnCreateObject)(); // 对象创建函数
CRuntimeClass* m_pBaseClass; //基类信息,各CRuntimeClass通过它连成单向树
// CRuntimeClass objects linked together in simple listCRuntimeClass* m_pNextClass; // 链表结构,记录相关类的完整容器const AFX_CLASSINIT* m_pClassInit;// OperationsCObject* CreateObject();BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;// dynamic name lookup and creationstatic CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);static CObject* PASCAL CreateObject(LPCSTR lpszClassName);
};
链表的头节点与树的根节点都是“特殊”节点,需要特殊处理,在MFC框架中CObject就是对应的特殊类。
实现
运行时识别
MFC的运行时识别与DECLARE_DYNAMIC及IMPLEMENT_DYNAMIC两个宏有关。以下是简化版:
//放到类声明中,一个类对应一个全局的CRuntimeClass
#define DECLARE_DYNAMIC(class_name)
public:
static const CRuntimeClass class##class_name;
virtual CRuntimeClass* GetRuntimeClass() const; \
//作为全局(静态)数据,初始化
#define IMPLEMENT_DYNAMIC(class_name, base_class_name)
const CRuntimeClass class_name::class##class_name = {
#class_name, sizeof(class class_name), 0xFFFF, NULL,
RUNTIME_CLASS(base_class_name), NULL, NULL };
CRuntimeClass* class_name::GetRuntimeClass() const
{ return RUNTIME_CLASS(class_name); }
RUNTIME_CLASS也是一个宏,是为了更方便的得到静态数据CRuntimeClass:
//这个宏是为了更方便的获取类里面的静态数据成员
//如RUNTIME_CLASS(CView) 等价于 ((CRuntimeClass*)(&CView::classCView))
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))
建立了单向树之后,在CObject中添加成员IsKindOf(),即可通过遍历单向树来判断继承关系:
BOOL CObject::IsKindOf(CRuntimeClass *pClass)
{
CRuntimeClass *pClassThis = GetRuntimeClass();
while(pClassThis != NULL && pClassThis != pClass)
{
pClassThis = pClassThis->m_pBaseClass;
}
if (pClassThis == NULL)
{return FALSE;
}return TRUE;
}
动态创建
简单而言,动态创建的最终目的,就是能够通过字符串来创建对象。这可以在RTTI的基础上附带一个创造对象的接口实现:
#define DECLARE_DYNCREATE(class_name)
DECLARE_DYNAMIC(class_name)
static CObject* PASCAL CreateObject();
实现:
#define IMPLEMENT_DYNCREATE(class_name, base_class_name)
CObject* PASCAL class_name::CreateObject()
{ return new class_name; }
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF,
class_name::CreateObject, 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); }
以上宏为每一个支持动态创建的类(而不是CRuntimeClass)多声明、实现了一个静态CreateObject()接口,用于获取一个新对象。并且将CreateObject()初始化至对应的CRuntimeClass中。
每一个CRuntimeClass对应一个类,我们也就知道CRuntimeClass中的CreatObject()如何实现了:
CObject * CRuntimeClass::CreateObject()
{
//…各检查
return (*m_pfnCreateObject)();
}
最终,我们只需要一个函数,它的输入参数为字符串,其实现中去遍历CRuntimeClass链表并作判断,当类型一致时就调用CRuntimeClass对象的CreateObject(),就实现了动态创建。这就是CRuntimeClass中的静态成员函数所做的事。
struct CRuntimeClass
{
//…
static CObject* PASCAL CreateObject(LPCSTR lpszClassName);
/* 实现:
- 从CRuntimeClass链表的首节点开始遍历(pClassThis = pFirst)
- 比较lpszClassName与pClassThis->m_lpszClassName
- 若相等,则调用并返回pClassThis->CreateObject()
*/
};
技术交流群,群内分享视频、就业资源:C++大学技术协会:145655849