CRuntimeClass类
在程序运行的过程中辨别对象是否属于特定类的技术叫动态类型识别(Runtime Type Infomation,RTTI)。当函数需要识别其参数类型的时候,或者是必须针对对象所属的类编写特殊目的的代码时,运行期识别就变得非常有用了。
框架程序使用这一技术管理应用程序是非常方便的,因为用户可以任意设计自己的类,框架程序可以在程序执行的时候知道这个类的类名、大小等信息,并能创建这个类的对象。
动态类型识别和动态创建
如何识别对象是否属于某个类呢?区别进程可以使用进程ID,区别窗口可以使用窗口句柄,要区别类的话,必须也要给类安排惟一的标识。类的静态成员不属于对象的一部分,而是类的一部分,也就是说在定义类的时候,编译器已经为这个类的静态成员分配内存了,不管实例化多少个此类的对象,类的静态成员在内存中都只有一份。因此,可以给每个类安排一个静态成员变量,此成员变量的内存地址就是这个类的标识!
下面是最简单的类型辨别系统。每个有类型识别能力的类都有一个静态成员变量,此变量的内存地址是类的惟一标识。因此,下面的代码可以在运行时动态判定对象student的类型。
05RTTI
#include <iostream>
using namespace std;
class CBoy
{
public:
// ... // 其它成员
public:
const int* GetRuntimeClass() { return &classCBoy; }
static const int classCBoy; // classCBoy成员的内存地址是CBoy类的唯一标识
};
const int CBoy::classCBoy = 1; // 随便初始化一个值就行了
class CGirl
{
public:
// ... // 其它成员
public:
const int* GetRuntimeClass() { return &classCGirl; }
static const int classCGirl; // classCGirl成员的内存地址是CGirl类的唯一标识
};
const int CGirl::classCGirl = 1;
void main()
{
CBoy student;
if(student.GetRuntimeClass() == &CGirl::classCGirl) // 用静态成员的地址辨别student对象是否属于CGirl类
cout << " a girl \n";
else if(student.GetRuntimeClass() == &CBoy::classCBoy)
cout << " a boy \n";
else
cout << " unknown \n";
}
上面的静态成员类型为int,其值并没有实际意义。为了在运行期间记录类的信息,可以用有意义的结构来描述此静态成员,将之命名为CRuntimeClass。
类的最基本信息包括类的名称、大小,所以CRuntimeClass结构里有这两个成员。
LPCSTR m_lpszClassName; // 类的名字
int m_nObjectSize; // 类的大小
在系统升级的过程中,类的成员很可能会发生变化,所以还要有一个成员来记录当前类的版本号。
UINT m_wSchema; // 类的版本号
如果为每个类都写一个创建该类的全局函数的话,就能够依靠从文件或用户的输入中取得此函数的内存地址,从而创建用户动态指定的类,这项技术就是所谓的动态创建。所以创建类的函数的地址也应该是类的一个属性,应记录在CRuntimeClass结构里。
CObject* (__stdcall* m_pfnCreateObject)(); // 创建类的函数的指针
CObject* CreateObject();
为了方便调用m_pfnCreateObject指向的函数,再编写CreateObject成员函数,代码如下。
CObject* CRuntimeClass::CreateObject()
{
if(m_pfnCreateObject == NULL)
return NULL;
return (*m_pfnCreateObject)(); // 调用创建类的函数
}
要想判断一个类是不是从另一个类继承的可以在CRuntimeClass结构里记录下其父类的CRuntimeClass结构的地址。
CRuntimeClass* m_pBaseClass; //其基类中CRuntimeClass结构的地址
有了这个成员很容易就可以写一个检查继承关系的函数IsDerivedFrom。
BOOL CRuntimeClass::IsDerivedFrom(const CRuntimeClass* pBaseClass) const
{
const CRuntimeClass* pClassThis = this;
while(pClassThis != NULL)
{
if(pClassThis == pBaseClass) // 判断标识类的CRuntimeClass的首地址是否相同
return TRUE;
pClassThis = pClassThis->m_pBaseClass;
}
return FALSE; // 查找到了继承结构的顶层,没有一个匹配
}
此函数用于检查当前CRuntimeClass结构所标识的类是否从指定的类继承。下面是定义CRuntimeClass结构的完整的代码,由于这是框架程序提供的一项基本服务,所以为它所在的文件命名为_AFX.H和OBJCORE.CPP。
// _AFX.H文件
#ifndef __AFX_H__
#define __AFX_H__
#include <windows.h>
// 运行期类信息
class CObject;
struct CRuntimeClass
{
// 属性(Attributes)
LPCSTR m_lpszClassName; // 类的名称
int m_nObjectSize; // 类的大小
UINT m_wSchema; // 类的版本号
CObject* (__stdcall* m_pfnCreateObject)(); // 创建类的函数的指针
CRuntimeClass* m_pBaseClass; // 其基类中CRuntimeClass结构的地址
// 操作(operations)
CObject* CreateObject();
BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;
// 内部实现(Implementation)
CRuntimeClass* m_pNextClass; // 将所有CRuntimeClass对象用简单链表连在一起
};
//... // 这里还有其他类的定义
#endif // __AFX_H__
要想使所有的类都具有运行期识别和动态创建的特性,必须有一个类做为继承体系的顶层,也就是说所有具有此特性的类都要从一个类继承,这个类不但能够使IsDerivedFrom函数顺利运行(提供了值为NULL的m_pBaseClass),还提供了名为IsKindOf的函数直接辨别对象是否属于特定类。由于这个是所有类的基类,所以将它命名为CObject,下面是定义它的代码,也在_AFX.H文件中。
// CObject 类
class CObject
{
public:
virtual CRuntimeClass* GetRuntimeClass() const;
virtual ~CObject();
// 属性(Attibutes)
public:
BOOL IsKindOf(const CRuntimeClass* pClass) const;
// 实现(implementation)
public:
static const CRuntimeClass classCObject; // 标识类的静态成员
};
inline CObject::~CObject() { }
// 下面是一系列的宏定义
// RUNTIME_CLASS宏用来取得class_name类中CRuntimeClass结构的地址
#define RUNTIME_CLASS(class_name) ((CRuntimeClass*)&class_name::class##class_name)
classCObject静态成员的初始化代码和类的实现代码都在OBJCORE.CPP文件中。
const struct CRuntimeClass CObject::classCObject =
{ "CObject"/*类名*/, sizeof(CObject)/*大小*/, 0xffff/*无版本号*/,
NULL/*不支持动态创建*/, NULL/*没有基类*/, NULL};
CRuntimeClass* CObject::GetRuntimeClass() const
{
// 下面的语句展开后就是“return ((CRuntimeClass*)&(CObject::classCObject));”
return RUNTIME_CLASS(CObject);
}
BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
{
CRuntimeClass* pClassThis = GetRuntimeClass();
return pClassThis->IsDerivedFrom(pClass);
}
RUNTIME_CLASS是为了方便访问类的CRuntimeClass结构而定义的宏。在这里可以看到每个类中CRuntimeClass成员变量的命名规则:在类名之前冠以class作为它的名字。class##class_name中的##告诉编译器,把两个字符串捆在一起。
GetRuntimeClass函数就使用了RUNTIME_CLASS宏。如果读者还不习惯,用注释上所述的等价语句也是可以的。
现在,为了给类添加运行期识别的能力,可以让该类从CObject类继承,然后再在类中添加CRuntimeClass类型的静态成员等信息。例如,在下面的例子中,CPerson类就具有了运行期识别的能力。程序在运行过程中调用CObject::IsKindOf函数判断一个对象是否属于CPerson类,是则转化对象指针,以便正确地删除,代码如下。
05TypeIdenify
// TypeIdentify.cpp文件
#include <iostream>
#include "_afx.h"
using namespace std;
class CPerson : public CObject
{
public:
virtual CRuntimeClass* GetRuntimeClass() const
{ return (CRuntimeClass*)&classCPerson; }
static const CRuntimeClass classCPerson;
};
const CRuntimeClass CPerson::classCPerson =
{ "CPerson", sizeof(CPerson), 0xffff, NULL, (CRuntimeClass*)&CObject::classCObject, NULL };
void main()
{
CObject* pMyObject = new CPerson;
// 判断对象pMyObject是否属于CPerson类或者此类的派生类
if(pMyObject->IsKindOf(RUNTIME_CLASS(CPerson)))
// RUNTIME_CLASS(CPerson)宏被展开后相当于((CRuntimeClass*)&CPerson::classCPerson)
{
CPerson* pMyPerson = (CPerson*)pMyObject;
cout << " a CPerson Object! \n";
delete pMyPerson;
}
else
{
delete pMyObject;
}
}
CObject类的成员函数IsKindOf可用于确定具体某个对象是否属于指定的类或指定的类的派生类。要注意,GetRuntimeClass是虚函数,CPerson类重载了它,所以在IsKindOf函数的实现代码中,“pClassThis = GetRuntimeClass();” 语句调用的是CPerson类的GetRuntimeClass函数,而不是CObject类的。为了明确地说明这一点,请看下面打印类信息的代码:
CRuntimeClass* pClass = pMyObject->GetRuntimeClass();
cout << pClass->m_lpszClassName << "\n"; // 打印出“CPerson”
cout << pClass->m_nObjectSize << "\n"; // 打印出“4”
运行上面的代码后,程序打印出的是CPerson类的信息,说明了pClass指针指向的是CPerson类的CRuntimeClass对象。
如果要想使CPerson类也支持动态创建,只需要在初始化classCPerson对象的时候传递一个创建CPerson对象的函数的地址就行了。很简单,为CPerson类再安排一个静态成员函数,此成员函数就负责创建CPerson对象,名称为CreateObject。
class CPerson
{
…… // 其他成员
static CObject* __stdcall CreateObject()
{ return new CPerson; }
}
为了将CreateObject函数传给classCPerson对象中的m_pfnCreateObject成员,修改该对象的初始化代码如下。
const CRuntimeClass CPerson::classCPerson =
{ "CPerson", sizeof(CPerson), 0xffff, &CPerson::CreateObject/*添加到这里*/, (CRuntimeClass*)&CObject::classCObject, NULL };
现在,只要得到了CPerson类中CRuntimeClass结构记录的类的信息,就可以动态创建CPerson类了。下面是动态创建CPerson类的例子。
05DynCreate
#include <iostream>
#include "_afx.h"
using namespace std;
class CPerson : public CObject
{
public:
virtual CRuntimeClass* GetRuntimeClass() const
{ return (CRuntimeClass*)&classCPerson; }
static const CRuntimeClass classCPerson;
static CObject* __stdcall CreateObject()
{ return new CPerson; }
};
const CRuntimeClass CPerson::classCPerson =
{ "CPerson", sizeof(CPerson), 0xffff, &CPerson::CreateObject/*添加到这里*/, (CRuntimeClass*)&CObject::classCObject, NULL };
void main()
{
// 取得CPerson类中CRuntimeClass结构记录的信息
// 在实际应用中,我们一般从磁盘上取得这一信息,
// 从而在代码中没有给出类名的情况下创建该类的对象
CRuntimeClass* pRuntimeClass = RUNTIME_CLASS(CPerson);
// 取得了pRuntimeClass指针,不用知道类的名字就可以创建该类
CObject* pObject = pRuntimeClass->CreateObject();
if(pObject != NULL && pObject->IsKindOf(RUNTIME_CLASS(CPerson)))
{
cout << "创建成功!\n";
delete pObject;
}
}
动态创建,这就是动态创建,只需要知道一个类的CRuntimeClass结构记录的信息,就可以在内存中创建这个类的对象。这些信息可以是用户在程序运行过程中输入的,也可以是从磁盘上取得的。
DECLARE_DYNAMIC等宏的定义
通过上面几个例子可以看到,支持类的运行期识别能力的代码是固定的:一个CRuntimeClass类型的静态变量,一个可以取得该对象地址的虚函数GetRuntimeClass。为了方便用户使用,最好设计一组宏来代替这些重复性代码。比如,在定义类的时候,用名称为DECLARE_DYNAMIC的宏来代替变量的定义和函数的声明。
// 支持动态类型识别的宏
#define DECLARE_DYNAMIC(class_name) \
public: \
static const CRuntimeClass class##class_name; \
virtual CRuntimeClass* GetRuntimeClass() const;
把这个宏定义放在_AFX.H文件中(RUNTIME_CLASS宏的后面)。与DECLARE(声明)相对应的就是IMPLEMENT(实现)了,所以再定义下面两个宏来代替初始化CRuntimeClass对象的代码和实现GetRuntimeClass函数的代码。
#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew) \
const CRuntimeClass class_name::class##class_name = { \
#class_name, sizeof(class class_name), wSchema, pfnNew, \
RUNTIME_CLASS(base_class_name), NULL }; \
CRuntimeClass* class_name::GetRuntimeClass() const \
{ return RUNTIME_CLASS(class_name); } \
#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)
提供IMPLEMENT_RUNTIMECLASS宏完全是为了方便,在实现不同的功能的时候直接给该宏传递不同的初始化参数就行了。例如,运行期识别的功能只对当前类的类名class_name和基类的类名base_class_name有要求,所以IMPLEMENT_DYNAMIC宏只有这两个参数,然后再添加上其他的默认值去调用IMPLEMENT_RUNTIMECLASS宏。
现在只需要使用DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC两个宏就可以使CObject的派生类拥有动态识别的功能。还是CPerson类这个例子,现在修改如下。
05DynClass
// DynClass.cpp
#include <iostream>
#include "_afx.h"
using namespace std;
class CPerson : public CObject
{
DECLARE_DYNAMIC(CPerson)
};
IMPLEMENT_DYNAMIC(CPerson, CObject)
void main()
{
CObject* pMyObject = new CPerson;
if(pMyObject->IsKindOf(RUNTIME_CLASS(CPerson)))
{
CPerson* pMyPerson = (CPerson*) pMyObject ;
cout << " a CPerson Object! \n";
delete pMyPerson;
}
else
delete pMyObject;
}
此时,用户不需要知道CObject是什么,不需要知道两个小巧的宏做了些什么,就可以方便地向自己的类中添加动态类型识别的功能。
在动态识别的基础上,向类里面添加一个创建该类的静态成员函数,就可以完成动态创建的功能了。所以依靠上面的两个宏,可以很容易地定义出支持动态创建的一组宏。按照它们的作用,为这组宏分别命名为DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE,下面是定义代码。
// 支持动态创建的宏
#define DECLARE_DYNCREATE(class_name) \
DECLARE_DYNAMIC(class_name) \
static CObject* __stdcall CreateObject();
#define IMPLEMENT_DYNCREATE(class_name, base_class_name) \
CObject* __stdcall class_name::CreateObject() \
{ return new class_name; } \
IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, \
class_name::CreateObject)
DYNCREATE是Dynamic Create的缩写,意思是“动态创建”。从它们的依赖关系可以看出,支持动态创建的类一定也支持动态识别。把这组宏用在前面的05DynCreate工程中,代码如下。
class CPerson : public CObject
{
DECLARE_DYNCREATE(CPerson)
};
IMPLEMENT_DYNCREATE(CPerson, CObject)
void main() // main函数里的代码没有变化
{
CRuntimeClass* pRuntimeClass = RUNTIME_CLASS(CPerson);
CObject* pObject = pRuntimeClass->CreateObject();
if(pObject != NULL && pObject->IsKindOf(RUNTIME_CLASS(CPerson)))
{
cout << " 创建成功!\n"; delete pObject;
}
}
运行结果和前面一样,打印出“创建成功!”的字符串。