欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++实现根据类名动态生成类对象

程序员文章站 2022-04-15 11:12:13
在开发后台服务的过程中,我们常常需要从中取数据,并将数据缓存在本地中,另外,我们的服务还需要有更新数据的能力:包括定时的主动更新以及数据库数据更新时服务收到通知的被动更新。 之前在需要用到以上功能的...

在开发后台服务的过程中,我们常常需要从中取数据,并将数据缓存在本地中,另外,我们的服务还需要有更新数据的能力:包括定时的主动更新以及数据库数据更新时服务收到通知的被动更新。

之前在需要用到以上功能的时候,模仿着组内通用的数据cache部分的代码来写,十分方便,基本上只需要自己写两个类:一个是取数据并缓存数据的类xxxdata,一个是扇出数据的类xxxfetcher。

在需要使用数据的时候,通过:

fetcherfactory::getfetcher()

即可获取一个xxxfetcher对象的指针。

查看fetcherfactory.h的备注:

/**
 * 配置中心, 存储各种配置, 会自动搜索项目中的fetcher实现类, 加载, 并 
 * 在收到刷新配置的通知时调用update方法
 * 需要在服务器启动的oninitialize中调用本类的initialize方法进行初始化
 */

重点:会自动搜索项目中的fetcher实现类并加载。

也就是说,fetcherfactory可以自动地查找项目中的fetcher实现类,然后动态地创建xxxfethcer对象。

什么叫动态创建呢?

就是能够根据一个类的名字动态地创建该类的对象。

举个例子,你的程序需要用到一个文本文件,而此文件中存放着一些类的名字,你能根据这些名字动态的生成这些类吗?

/*****************************************************************************/
/*从文件中读出类的名字存放在字符串变量szclassname中,现在假设读出的字符串为 */
/* ”cstring”,而类cstring派生自cobject,那么我们可以用基类指针指向派生类 */
/***********************************/*****************************************/
char szclassname[20]
cobject *pob;
pob = new szclassname;  /* 概念版本 */

当然,以上只是一个概念版本,因为c++并不支持根据类名动态创建对象。c++中,操作符new后面只能跟数据结构的名称,不能跟字符串。
那么,有什么方式来达到“根据名字动态生成类对象”的目的吗?

最简单粗暴的方法就是:用嵌套的if…else…语句,反复比较读出的类名是否与预期的相同,然后生成此类的对象。的确,这是解决办法之一,但是如果程序要用到的类很多,而且经常会改变或添加,岂不是要经常改写识别字符串的函数?那样的话,会很繁琐且容易出错。

那么小组代码里的“动态创建”是如何实现的呢?

先看看fetcher类的备注:

/**
 * 自动加载的逻辑类, 会在fetcherfactory::initialize中被自动加载, 在fetcherfactory::update时循环调用所有fetcher::update方法
 * 其实现类必须满足下述条件:
 * 1. 继承自本类
 * 2. 以fetcher为类名结尾, 例如xxxfetcher, yyyfetcher
 * 3. 在.h和.cpp文件中分别添加declare_fetcherholder_dyncreate和implement_fetcherholder_dyncreate宏
 * 4. 项目中不能有任何tc_dyn_object子类同名
 *
 * 模拟mfc,动态生成类
 */

看来还是要先学习一下mfc动态生成类对象的方法。

下面我们简单模拟下mfc的实现方法。

基础知识:

class cexample
{
private:
int x;
public:
int y;
static long z;
… /*其它数据和函数*/
};

cexample j1,j2;

j1,j2两个对象共用一份变量z,就是说,如果一个成员变量被冠以static,那么无论这个类定义过多少对象,此成员变量在内存中始终只有一份。其实,变量z在类cexample还没有定义任何对象之前就已经在内存中实际存在了。我们可以利用这一特点来实现类的动态识别和生成。

在mfc中,有一个特殊的类cruntimeclass,它是实现上述功能最关键的地方。

cruntimeclass的声明:

class cobject;      /* 声明cobject是一个类,将在以后定义 */
struct cruntimeclass
{
char *m_lpszclassname;  /* 存放类名 */
int m_nobjectsize;          /* 存放对象大小 */
cobject*    (*m_pfncreateobject) (); /* 指向函数的指针,这个函数返回一个cobject  */
cruntimeclass* m_pnextclass;    /* 类型的指针 */
static cruntimeclass* pfirstclass;  /* 静态变量,链表头部 */
static cobject* createobject(const char* lpszclassname); /* 根据类名创建对象 */
};

接下来要的事情是:每个(我们想根据类名生成对象的)类都有一个静态的cruntimeclass成员变量(因此每个类有且只有一份),并且该变量有一定的命名规则,比如下面例子中的classxxx。然后,通过链表将这些变量串起来,进而可以通过cruntimeclass::pfirstclass来获取这些变量。

为了以后的方便性(同时也为了保密性),我们定义两个宏:

/* 声明 */
#define declare_dynamic(class_name)\
public:\
//定义cruntimeclass成员变量
static cruntimeclass class##class_name;\
//获取cruntimeclass变量的接口
virtual cruntimeclass* getruntimeclass() const;
/* 实现 */
#define implement_dynamic(class_name)\

//全局字符串,保存了本类的类名
char _lpsz##class_name[]=#class_name;\

//new一个本类的对象,非成员函数
cobject* create##class_name()\
{ return new class_name; }\

//初始化cruntimeclass静态成员变量,将类名、上面的create函数地址传进去
cruntimeclass class_name::class##class_name={\  
_lpsz##class_name,sizeof(class_name),create##class_name};\

//定义一个class_init类型的对象,使用cruntimeclass成员变量的地址构造
static class_init _init_##class_name(&class_name::class##class_name);\

//返回cruntimeclass成员变量的地址
cruntimeclass* class_name::getruntimeclass() const\
{ return &class_name::class##class_name; }

出现在宏定义中的##告诉编译器,把两个字符串捆绑在一起。

其中class_init定义如下:

struct class_init
{
    class_init(cruntimeclass* pnewclass);
};

class_init 的构造函数实现如下:

class_init::class_init(cruntimeclass* pnewclass)
{
    pnewclass->m_pnextclass = cruntimeclass::pfirstclass;
    cruntimeclass::pfirstclass = pnewclass;
}

于是各个类的cruntimeclass成员变量都通过链表串联起来了,链表头为cruntimeclass::pfirstclass(一个静态成员变量)。

这样,我们只要在类的定义文件(.h文件)中放入宏declare_dynamic,在类的实现文件(.cpp文件)中放入宏implement_dynamic,就可以实现我们的目的。

举个例子:

//cobject.h
class cobject{
declare_dynamic(cobject)
}

//cobject.cpp
implement_dynamic(cobject)

编译器做出来的是:

//cobject.h
class cobject{
public:
static cruntimeclass classcobject;
virtual cruntimeclass* getruntimeclass() const;
}

//cobject.cpp

char _lpszcobject[]=”cobject”;

cobject* createcobject()
{ return new cobject; }

cruntimeclass cobject::classcobject={_lpszcobject,sizeof(class_name),createcobject};

static class_init _init_cobject(&cobject::classcobject);

cruntimeclass* cobject::getruntimeclass() const
{ return &cobject::classcobject; }

假设我们有3个类a,b,c,它们均继承自cobject,并分别添加了两个宏,当我们对代码进行编译之后,会发生什么事情呢?

为类cruntimeclass创建一个静态变量pfirstclass,该变量为链表的头指针; 为类a、b、c各创建一个cruntimeclass类型的静态变量; 每个类的.cpp文件中创建一个class_init类型的静态变量,在该变量的构造函数中,将步骤2中类a、b、c的cruntimeclass类型的变量插入到链表的头部。

这样子,只要我们分别在类的定义和声明中加入两个宏declare_dynamic和implement_dynamic,并让所有的类继承自cobject,便可得到一个链表,该链表保存了各个类中cruntimeclass类型的变量,链表头部为cruntimeclass::pfirstclass。

有了这个链表,我们便可以遍历整个链表,通过

cruntimeclass::createobject(lpszclassname)

方法根据类名lpszclassname来创建相应的对象了~

于是我们就在不修改已经做好的代码的情况下,实现类对象的动态识别和生成的目的。以后,只要所有的类都派生自类cobject,并相应地加入两个宏,就具有了动态识别和生成的能力。

(以上代码比较简略,不一定十分准确,意在直观地理解、感受下这种实现的原理。)

通过学习,我们已经大致知道怎么用c++实现动态生成类对象的功能了。下一篇我将此基础之上,分析下本文开头讲到的数据cache以及更新的相关功能代码。