运行时类型识别(RTTI)

来源:互联网 发布:开机无法启动windows 编辑:程序博客网 时间:2024/04/30 04:21

运行时类型识别(RTTI)即是程序执行过程中知道某个对象属于某个类,我们平时用C++编程接触的RTTI一般是编译器的RTTI,即是在新版本的VC++编译器里面选用“使能RTTI”,然后载入typeinfo.h文件,就可以使用一个叫typeid()的运算子,它的地位与在C++编程中的sizeof()运算子类似的地方(包含一个头文件,然后就有一个熟悉好用的函数)。typdid()关键的地方是可以接受两个类型的参数:一个是类名称,一个是对象指针。所以我们判别一个对象是否属于某个类就可以象下面那样:
if (typeid (ClassName)== typeid(*ObjectName)){
((ClassName*)ObjectName)->Fun();
}

象上面所说的那样,一个typeid()运算子就可以轻松地识别一个对象是否属于某一个类,但MFC并不是用typeid()的运算子来进行动态类型识别,而是用一大堆令人费解的宏。大家在这里很疑惑,好象MFC在大部分地方都是故作神秘。使们我们编程时很迷惘,只知道在这里加入一组宏,又在那儿加入一个映射,而不知道我们为什么要加入这些东西。

其实,早期的MFC并没有typeid()运算子,所以只能沿用一个老办法。甚至可以想象一下,如果MFC早期就有template(模板)的概念,可能更容易解决RTTI问题。

要登记一系列的消息并不是一件简单的事情,可能想到用数组登记对象。但如果用数组,要定义多大的数组才好呢,大了浪费空间,小了更加不行。所以要用另一种数据结构——链表。因为链表理论上可大可小,可以无限扩展。

链表是一种常用的数据结构,简单地说,它是在一个对象里面保存了指向下一个同类型对象的指针。大体可以这样设计我们的类:
struct CRuntimeClass
{
  ……类的名称等一切信息……
  CRuntimeClass * m_pNextClass;         //指向链表中下一个CRuntimeClass对象的指针
};

链表还应该有一个表头和一个表尾,这样在查链表中各对象元素的信息的时候才知道从哪里查起,到哪儿结束。还要注明本身是由哪能个类派生。所以链表类要这样设计:
struct CRuntimeClass
{
  ……类的名称等一切信息……
  CRuntimeClass * m_pBaseClass;         //指向所属的基类。
  CRuntimeClass * m_pNextClass;         //定义表尾的时候只要定义此指针为空就可以了。
  static CRuntimeClass* pFirstClass; 
 //因为static变量在内存中只初始化一次,就可以为各对象所用.保证了各对象只有一个表头。
};

有了CRuntimeClass结构后,就可以定义链表了:
static CRuntimeClass classCObject={NULL,NULL};
//这里定义了一个CRuntimeClass对象,因为classCObject无基类,所以m_pBaseClass为NULL。
//因为目前只有一个元素(即目前没有下一元素),所以m_pNextClass为NULL(表尾)。

因为我们这里并不想把classCObject作为链表表头,还要在前面插入很多的CRuntimeClass对象,并且因为pFirstClass为static指针,即它不是属于某个对象,所以我们在用它之前要先初始化:CRuntimeClass* CRuntimeClass::pFirstClass=NULL;
现在在前面插入一个CRuntimeClass对象,插入之前申明一下:如果单纯为了运行时类型识别,未必用到m_pNextClass指针(更多是在运行时创建时用),我们关心的是类本身和它的基类。这样,查找一个对象是否属于一个类时,主要关心的是类本身及它的基类,
CRuntimeClass classCCmdTarget={ &classCObject, NULL};
CRuntimeClass classCWnd={ &classCCmdTarget ,NULL };
CRuntimeClass classCView={ &classCWnd , NULL };

上面只是仅仅为一个指针m_pBaseClass赋值(MFC中真正CRuntimeClass有多个成员变量和方法),就连接成了链表。假设现在已全部构造完成自己需要的CRuntimeClass对象,那么,这时候应该定义表头。即要用pFirstClass指针指向我们最后构造的CRuntimeClass对象——classCView。
CRuntimeClass::pFirstClass=&classCView;

现在链表有了,表头表尾都完善了,问题又出现了,应该怎样访问每一个CRuntimeClass对象?我们构造这个链表的目的,就是构造完之后,能够按主观地拿一个CRuntimeClass对象和链表中的元素作比较,看看其中一个对象中否属于指定的类。这样,我们需要有一个函数,一个能返回自身类型名的函数GetRuntimeClass()。

上面简单地说一下链表的过程,但单纯有这个链表是没有任何意义。回到MFC中来,我们要实现的是在每个需要有RTTI能力的类中构造一个CRuntimeClass对象,比较一个类是否属于某个对象的时候,实际上只是比较CRuntimeClass对象。
如何在各个类之中插入CRuntimeClass对象,并且指定CRuntimeClass对象的内容及CRuntimeClass对象的链接,这里起码有十行的代码才能完成。在每个需要有RTTI能力的类设计中都要重复那十多行代码是一件乏味的事情,也容易出错,所以MFC用了两个宏代替这些工作,即DECLARE_DYNAMIC(类名)和IMPLEMENT_DYNAMIC(类名,基类名)。从这两个宏我们可以看出在MFC名类中的CRuntimeClass对象构造连接只有类名及基类名的不同。

为什么要用两个宏,用一个宏不可以代换CRuntimeClass对象构造连接吗?个人认为肯定可以,因为宏只是文字代换而已。但在编程之中,头文件与源文件是分开的,我们要在头文件头声明变量及方法,在源文件里实具体实现。即是说我们要在头文件中声明:
public:
static CRuntimeClass classXXX  //XXX为类名
virtual CRuntime* GetRuntimeClass() const;
然后在源文件里实现:
CRuntimeClass* XXX::classXXX={……};
CRuntime* GetRuntimeClass() const;
{ return &XXX:: classXXX;}
//这里不能直接返回&classXXX,因为static变量是类拥有而不是对象拥有。

MFC中的DECLARE_DYNAMIC(类名)宏应该这样定义:
#define DECLARE_DYNAMIC(class_name) public: static CRuntimeClass class##class_name; virtual CRuntimeClass* GetRuntimeClass() const;
其中##为连接符,可以让传入的类名前面加上class,否则跟原类同名,大家会知道产生什么后果。
有了上面的DECLARE_DYNAMIC(类名)宏之后,我们在头文件里写上一句:
DECLARE_DYNAMIC(XXX)
宏展开后就有了我们想要的:
public:
static CRuntimeClass classXXX  //XXX为类名
virtual CRuntime* GetRuntimeClass() const;

对于IMPLEMENT_DYNAMIC(类名,基类名)也是同理。
有了此链表之后,就像有了一张存放各类型的网,我们可以轻而易举地使用RTTI。CObject有一个函数BOOL IsKindOf(const CRuntimeClass* pClass) const;,被它以下所有派生员继承。
此函数实现如下:
BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
{
  CRuntimeClass* pClassThis=GetRuntimeClass();  //获得自己的CRuntimeClass对象指针。
  while(pClassThis!=NULL)
  {
   if(pClassThis==pClass) return TRUE;
   pClassThis=pClassThis->m_pBaseClass;   

//这句最关键,指向自己基类,再回头比较,一直到尽头m_pBaseClass为NULL结束。
  }
   return FALSE;
}


 
原创粉丝点击