char szClassName[20]
/*****************************************************************************/
/*从文件中读出类的名字存放在字符串变量szClassName中,现在假设读出的字符 */
/*”CString”,而类CString派生自CObject,那么我们可以用基类指针指向派生类 */
/***********************************/*****************************************/
CObject *pOb;
pOb = new szClassName; /* 看出有问题了吧*/
这只是”语言版”动态生成,编译器是无论如何不肯正确编译的,操作符new后面只能数据结构的名称,不能跟字符串.看到这里,也许有的读者会说,我们可以用嵌套的if…else…语句,反复比较读出的类名是否与预期的相同,然后生成此类.的确,这是解决办法之一.但是,如果程序要用到的类很多,而且经常会改变或添加,岂不是要经常改写识别字符串的函数?那样的话,会很繁琐且容易出错.
熟悉VC++的MFC程序设计的人都知道,在MFC中,有一个特殊的类CRuntimeClass,其实这就是实现上述功能最关键的地方.通过分析MFC实现动态识别和生成的方法,发现其基本原理非常简单.现在,我们用最少的程序量,在DOS中把它模拟出来.
在正式介绍类CRuntimeClass的数据结构之前,先给C++语言的初学者讲述一个基本知识.请看如下代码:
class CExample
{
private:
int x;
public:
int y;
static long z;
… /*其它数据和函数*/
};
CExample j1,j2;
j1,j2二个对象共用一份变量z,就是说,如果一个成员变量被冠以static,那么无论这个类定义过多少对象,此成员变量在内存中始终只有一份.其实,变量z在类CExample还没有定义任何对象之前就已经在内存中实际存在了.我们可以利用这一特点来实现类的动态识别和生成.
现在正式讲述类CRuntimeClass.请看:
(以下的变量? 有的仿照MFC的命名方式,其功用也跟MFC的差不多.)
class CObject; /*说明CObject是一个类,将在以后定义.*/
struct CRuntimeClass /*别惊讶,在C++语言中,struct和class差不多相同, */
{ /*只不过struct默认的是public,而class默认的 */
char *m_lpszClassName; /*是private,存储权限不同而已. */
int m_nObjectSize;
unsigned int m_wSchema;
CObject* (*m_lpfnConstruct) (); /*指向函数的指针,这个函数返回一个CObject */
CRuntimeClass* m_pNextClass; /* 类型的指针 */
static CRuntimeClass* pFirstClass;
static CObject* CreateObject(const char* lpszClassName);
};
我希望每个类都有一个CRuntimeClass成员变量(有且只有一份),并且有一定的命名规则.然后,利用一些手段将这些变量串联起来.
为了以后编程的方便性(同时也为了保密性),我们定义两个宏.
#define DECLARE_DYNAMIC(class_name)\ /*名称也有点相同*/
public:\
static CRuntimeClass class##class_name;\
virtual CRuntimeClass* GetRuntimeClass() const;
#define IMPLEMENT_DYNAMIC(class_name,wSchema)\
char _lpsz##class_name[]=#class_name;\
CObject* Create##class_name()\
{ return new class_name; }\
CRuntimeClass class_name::class##class_name={\
_lpsz##class_name,sizeof(class_name),wSchema,Create##class_name};\
static CLASS_INIT _init_##class_name(&class_name::class##class_name);\
CRuntimeClass* class_name::GetRuntimeClass() const\
{ return &class_name::class##class_name; }
出现在宏定义中的##告诉编译器,把两个字符串捆绑在一起.其中CLASS_INIT定义如下:
struct CLASS_INIT
{
CLASS_INIT(CRuntimeClass* pNewClass); /*是的,struct也可以有构造函数*/
};
这样,我们只要在类定义中放入宏DECLARE_DYNAMIC,在类的实现文件(就是.cpp文件)中放入宏IMPLEMENT_DYNAMIC,就可以神不知鬼不觉得实现我们的目的.
整个宏看起来有点吓人,其实没什么,替换而已.如果你这么使用宏:
DECLARE_DYNAMIC(CObject)
和
IMPLEMENT_DYNAMIC(CObject,0xFFFF)
编译器做出来的是:
public:
static CRuntimeClass classCObject;
virtual CRuntimeClass* GetRuntimeClass() const;
和
char _lpszCObject[]=”CObject”;
CObject* CreateCObject()
{ return new CObject; }
CRuntimeClass CObject::classCObject={
_lpszCObject,sizeof(class_name),0xFFFF,CreateCObject};
static CLASS_INIT _init_CObject(&CObject::classCObject);
CRuntimeClass* CObject::GetRuntimeClass() const
{ return &CObject::classCObject; }
整个宏看起来象是赋初值,其实不然,奥妙在于定义了一个结构CLASS_INIT ,其构造函数如下:
CLASS_INIT::CLASS_INIT(CRuntimeClass* pNewClass)
{
pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pNewClass;
}
这样,就把个各类的CRuntimeClass成员变量串联起来.让我们看一个实际例子.
class CObject /*这些代码分别定义在各个头文件中*/
{
private:
…
public:
…
DECLARE_DYNAMIC(CObject)
};
class CShape:public CObject
{
private:
…
public:
…
DECLARE_DYNAMIC(CShape)
};
class CRectangle:public CShape
{
private:
…
public:
…
DECLARE_DYNAMIC(CRectangle)
};
class CEllipse:public CShape
{
private:
…
public:
…
DECLARE_DYNAMIC(CEllipse)
};
/*以下的代码分别在各个类的实现文件中*/
CRuntimeClass * CRuntimeClass::pFirstClass; /*由于是static类型的变量,需在第一次 */
/*使用前说明一下 */
IMPLEMENT_DYNAMIC(CObject,0x0001)
IMPLEMENT_DYNAMIC(CShape,0x0002)
IMPLEMENT_DYNAMIC(CRectangle,0x0003)
IMPLEMENT_DYNAMIC(CEllipse,0x0004)
最后说明一下在类中的CreateObject函数,这个函数其实很简单,就是根据上面的链表,一个一个比较类名是否相同,如果相同则调用相应的构造函数动态生成此类,返回指向此类的指针(当然是利用基类指针指向派生类).这样,我们就利用两个宏DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC,在不修改已经做好的代码的情况下,实现了我们动态识别和生成的目的.以后,只要所有的类都派生自类CObject,就具有了动态识别和生成的能力.
上面的这些技巧都是从MFC中学到的,各位如果有兴趣的话,可以看一下MFC的源代码,在里面有更富技巧性的做法,当然也更复杂.