C++利用宏实现反射和运行时类型检测

我们知道,C++是没有反射机制的,很多程序员在用C++时遇到此问题也是挠头不已,下面,我就仿照《深入浅出MFC》中的动态生成一节定义自己的宏实现反射和运行时类型检测

一、首先,定义一个运行时结构体,我把它起名为CMyRuntime

struct CMyRuntime
{
std::string m_lpcstrClassName;
CMyRuntime*  m_pBaseClass;
CMyBase* (PASCAL* m_pfnCreate)();
CMyRuntime*  m_pNextClass;
static CMyRuntime* pFirstClass;
CMyBase* CreateInstance();
static CMyRuntime* PASCAL Load( std::string type);
};

需要说明一下每个成员的含义:

1.m_lpcstrClassName是指为下面所述的每个类生成一个独一无二的名称,将来反射或类型检测时实际上就是通过比较这个字符串,确定其所属的。

2.m_pBaseClass是指向其父类的指针,这样说不确切,因为它的类型是CMyRuntime,先留着不讲,等看了代码就明白了。

3. (PASCAL* m_pfnCreate)()是指其动态创建的指针函数,其返回值CMyBase就是指所有具有反射机制的类的基类,调用它即返回一个子类的对象。

4.m_pNextClass是指向下一个类的指针,为什么有这个是因为我们把所有类都串联成了一个链表,反射和类型检测就是沿着这个链表一个一个的查找类名来实现的。这个类名就是m_lpcstrClassName

5.pFirstClass是指向这个链表的头指针,它是个静态变量,因为这个链表只有一个,当然头指针也只有一个了。

6.CreateInstance这个函数是m_pfnCreate的具体实现,它的作用就是返回一个对象。

7.Load这个函数就是反射的实现函数,它的功能就是遍历链表,找出由type参数所指向的类。

下面是CMyRuntime里面的函数的具体实现:


CMyRuntime* CMyRuntime::pFirstClass = NULL;

CMyBase* CMyRuntime::CreateInstance()
{
return (*m_pfnCreate)();
}

CMyRuntime* CMyRuntime::Load(string type)
{
CMyRuntime* pClass;
for (pClass = pFirstClass;pClass!=NULL;pClass = pClass->m_pNextClass)
{
if(type == pClass->m_lpcstrClassName)
return pClass;
}
return NULL;
}

看了代码是不是有点明白了,不明白也没关系,下面我们进入主要的部分,宏定义。

二、定义一个声明宏,我将它写成下面这个样子

#define DECLARE_DYNAMIC_MY(classname)\
public:\
static  CMyRuntime class##classname;\
virtual CMyRuntime* GetRuntimeClass() const;

它写到类的头文件声明中,如:

class CMyBase
{
DECLARE_DYNAMIC_MY(CMyBase)

...

}

它实际上是声明了一个叫class##classname的静态变量,类型是CMyRuntime,##的意义是将左右两边连成一个字符串,有感到晦涩的,还要多查一查C++资料啊

另外还声明了一个GetRuntimeClass的虚函数,返回类型是CMyRuntime*


有了声明当然要有实现,我将实现声明为

#define IMPLEMENT_DYNAMIC_MY(classname,base_classname)\
IMPLEMENT_RUNTIME_MY(classname,base_classname,NULL)

#define IMPLEMENT_RUNTIME_MY(classname,base_classname,pfnNew)\
static std::string my##classname = #classname;\
CMyRuntime  classname::class##classname = {my##classname,\
RUNTIME_CLASS_MY(base_classname),pfnNew,NULL};\
static init_Runtime init_Runtime_##classname(&classname::class##classname);\
CMyRuntime* classname::GetRuntimeClass() const {return &classname::class##classname;}

具体解释一下,第一个宏调用了第二个宏,只是将第三个参数设置为NULL罢了,为什么要写两个,这个是为了下一个宏好节省代码,不需多讲

重点看#define IMPLEMENT_RUNTIME_MY(classname,base_classname,pfnNew)

这个宏会定义在类的实现文件.cpp中,如 IMPLEMENT_DYNAMIC_MY(CMyDerived1,CMyBase),第一个参数是派生类,第二个参数是基类

1、首先,定义了一个静态的字符串my##classname,这样定义是为了使这个字符串唯一,如CMyDerived1,这个字符串就是myCMyDerived1而CMyDerived2,这个字符串就是myCMyDerived2,这样它们就不会有命名冲突了,#classname是这个字符串的值,当然也是唯一的,#跟##是不一样的,##是连接,#是将后面作为一个字符串处理

如#CMyDerived1就是"CMyDerived1",#CMyDerived2就是"CMyDerived2",因为CMyDerived1和CMyDerived2名字不可能一样,因此#classname也一定是惟一的

2、CMyRuntime  classname::class##classname = {my##classname,\
RUNTIME_CLASS_MY(base_classname),pfnNew,NULL};\

这句话是指对头文件声明的class##classname变量赋值,我们知道,C++的类是可以通过逐个类成员赋值的,虽然不提倡这样做,但是也是可行的。在这里,我们就用这个特征给CRuntime的各个成员进行了赋值,复制后class##classname的各个成员变量为:

m_lpcstrClassName:my##classname

m_pBaseClass:RUNTIME_CLASS_MY(base_classname)

这里还涉及一个宏,#define RUNTIME_CLASS_MY(classname)\

(&classname::class##classname),其父类的静态变量的指针,这也是为什么它的类型是CMyRuntime。

m_pfnCreate:NULL,即现在还不能动态反射

m_pNextClass:NULL,它的下一个类是空

3、接下来是static init_Runtime init_Runtime_##classname(&classname::class##classname);\

这里用到了一个类型init_Runtime,我将它这样定义:

struct init_Runtime
{
init_Runtime(CMyRuntime* pClass);
};

init_Runtime::init_Runtime(CMyRuntime* pClass)
{
pClass->m_pNextClass = CMyRuntime::pFirstClass;
CMyRuntime::pFirstClass = pClass;
}

这句话的意思就是定义了一个init_Runtime的静态变量init_Runtime_##classname,##的作用相信大家应该都明白了,就是为了使这个变量名唯一,。

为什么要定义这样一个变量呢,其实不是为了定义变量,而是为了让它执行构造函数(没办法,除了定义变量让其执行好像也没有其他法子)

它的构造函数的作用就是将这个类嵌入到链表中

4、CMyRuntime* classname::GetRuntimeClass() const {return &classname::class##classname;}\

这个就是返回类的静态变量class##classname的指针,不需多说。

有了这四个宏,我们就将这个链表建立起来了。

三、我们再定义两个宏

#define DECLARE_DYCREATE_MY(classname)\
DECLARE_DYNAMIC_MY(classname)\
static CMyBase* PASCAL CreateInstance();

这个宏比DECLARE_DYNAMIC_MY多了一个函数,CreateInstance就是生成对象

#define IMPLEMENT_DYCREATE_MY(classname,base_classname)\
CMyBase* PASCAL classname::CreateInstance(){return new classname;}\
IMPLEMENT_RUNTIME_MY(classname,base_classname,classname::CreateInstance)

看到了吧,new出来一个新对象啦

于是,我们完成了反射,赶紧做个实验。

定义:

class CMyBase
{
DECLARE_DYNAMIC_MY(CMyBase)
public:
CMyBase(void);
~CMyBase(void);
};

CMyRuntime CMyBase::classCMyBase = {"CMyBase",NULL,NULL,NULL};




CMyRuntime* CMyBase::GetRuntimeClass() const
{
return RUNTIME_CLASS_MY(CMyBase);
}


CMyBase::CMyBase(void)
{
}


CMyBase::~CMyBase(void)
{
}


class CMyDerived1:public CMyBase
{
DECLARE_DYCREATE_MY(CMyDerived1)
public:
CMyDerived1(void);
~CMyDerived1(void);
};

IMPLEMENT_DYCREATE_MY(CMyDerived1,CMyBase)


CMyDerived1::CMyDerived1(void)
{
}


CMyDerived1::~CMyDerived1(void)
{
}


class CMyDerived2 :
public CMyBase
{
DECLARE_DYCREATE_MY(CMyDerived2)
public:
CMyDerived2(void);
~CMyDerived2(void);
};

IMPLEMENT_DYCREATE_MY(CMyDerived2,CMyBase)


CMyDerived2::CMyDerived2(void)
{
}

CMyDerived2::~CMyDerived2(void)
{
}


然后在main函数中实验

CMyDerived1* derived1 =(CMyDerived1*)CMyRuntime::Load("CMyDerived1")->CreateInstance();
CMyDerived2* derived2 =(CMyDerived2*)CMyRuntime::Load("CMyDerived2")->CreateInstance();

看到了吧,这就是反射的实现,由此,我怀疑是否C#的反射也是这样形成的呢,为什么它的所有类型都有个基类object呢,值得思考,在此就不敢臆测了。



四、以上是反射的实现,接下来是运行时类型检测,我们通常都用dynamic_cast,但它是从上而下的,可以检测基类指针或引用是否可以安全转换为子类指针或引用,但从子类到基类就不管用了,有人会建议说用type_info来检测,但type_info是严格意义上的类型比较,只能检测两个指针或引用是否是同一类型的,那遇上子类到基类的类型检测该怎么办呢,很好办,我们将上面宏再改造一下

#define DECLARE_DYCREATE_MY(classname)\
DECLARE_DYNAMIC_MY(classname)\
static CMyBase* PASCAL CreateInstance();\
virtual bool is_kindof(CMyRuntime* pBase);

看到多了一个函数is_kindof,这就是类型检测的实现函数

#define IMPLEMENT_RUNTIME_MY(classname,base_classname,pfnNew)\
static std::string my##classname = #classname;\
CMyRuntime  classname::class##classname = {my##classname,\
RUNTIME_CLASS_MY(base_classname),pfnNew,NULL};\
static init_Runtime init_Runtime_##classname(&classname::class##classname);\
CMyRuntime* classname::GetRuntimeClass() const {return &classname::class##classname;}\
bool classname::is_kindof(CMyRuntime* pBase){CMyRuntime* pClass = RUNTIME_CLASS_MY(classname);\
for (;pClass!=NULL;pClass=pClass->m_pBaseClass)\
if (pBase->m_lpcstrClassName == pClass->m_lpcstrClassName)\
return true;\
return false;}

这个比原来多了一个is_kindof的实现,它也是在遍历这个链表,反射是用m_pNextClass,而它是用m_pBaseClass,很神奇吧,就这点区别.

好了,它已经具有从下而上类型检测的功能了,赶紧试验一下

bool res = derived1->is_kindof(RUNTIME_CLASS_MY(CMyBase));
bool res1 = derived1->is_kindof(RUNTIME_CLASS_MY(CMyDerived2));

检测结果,res==true,res1==false


小注:在这个实验过程中,也许您会遇到头文件相互包含的问题,不才就遇到过,请参考我的另一篇拙文.


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神气爱哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值