什么时候RTTI会用得上

来源:互联网 发布:不用root的字体软件 编辑:程序博客网 时间:2024/04/30 04:56

If you have derived your class from CObject and used the DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC, the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE, or the DECLARE_SERIAL and IMPLEMENT_SERIAL macros explained in the article CObject Class: Deriving a Class from CObject, the CObject class has the ability to determine the exact class of an object at run time.

摘自MSDN,意思是你的类如果从CObject派生并且用了DECLARE_DYNAMIC /IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE / IMPLEMENT_DYNCREATE, 或者 DECLARE_SERIAL /IMPLEMENT_SERIAL 宏,那么你的类就能够提供运行时类型信息.

很多朋友一看RTTI就知道是"运行时类型信息"的意思,但并不知道什么时候RTTI会用得上,在MFC中,与C++的机制是不一样的,下面我写点关于RTTI的实现.肯定有很多错误的,还是那句话,欢迎拍砖指导.

什么时候用RTTI? 在这样的代码中:

class base{.....};

class deri: public base{......};

void func()

{

            base*p=new deri;

}

这就是所谓的C++动态创建,base指针指着一个deri类的对象, 有时候,我们需要知道deri的类型,这时就要用到RTTI,到底什么时候要用到呢?比如,我们要根据对象的类型做不同的处理,如打印一行信息,另外如永久保存,数据读写时,就需要类型识别! 

MFC中RTTI的实现依赖于CRunTime类,下面的是该类的一些成员:

CRuntimeClass Class Members

CreateObject()   Creates an object during run time.

FromName()   Creates an object during run time using the familiar class name.

IsDerivedFrom()  Determines if the class is derived from the specified class.

CRuntimeClass Data Members

m_pBaseClass   A pointer to the CRuntimeClass structure of the base class.

m_lpszClassName  The name of the class.

m_nObjectSize   The size of the object in bytes.

m_pfnCreateObject  A pointer to the function that dynamically creates the object.

m_pfnGetBaseClass  Returns the CRuntimeClass structure (only available when dynamically linked).

m_wSchema   The schema number of the class. //版本号!!

m_pNextClass  A pointer to the next node of the object list.

 注:侯俊杰先生在其<<深入浅出MFC>>中,对RTTI进行仿真的过程中为CRuntimeClass加入了成员m_pFirstClass,导致很多读者以为MFC中该类真有一个这样的成员,甚至没看MFC的CRuntimeClass源代码就以讹传讹,实在不严谨.

本来,在C/C++中有一个typeid操作符(类似于sizeof),它可以判断对象的类型,用法如下:

 Devi objDevi;  //Devi是本人自定义的一个类;

 if (typeid(Devi)==typeid(objDevi))

 {

  cout<<"objDevi is a object of class Devi"<<endl;

 }

 const type_info& tpdevi=typeid(objDevi);

 cout<<tpdevi.name()<<endl;               //这里得出是class Devi的类型,用于人识别

 cout<<tpdevi.raw_name()<<endl;   //这里返回的是内存表示,用于计算机识别

 可惜的是typeid比MFC的RTTI晚出现, MFC中的RTTI没有使用typeid,而是用了上面提到的一系列的宏,什么要求用什么宏,MSDN同样有说明:

Macros Used for Serialization and Run-Time Information

 

Macro used

 

CObject::IsKindOf

CRuntimeClass::

 

CreateObject

CArchive::operator>>

 

CArchive::operator<<

Basic CObject functionalityNoNoNoDECLARE_DYNAMICYesNoNoDECLARE_DYNCREATEYesYesNoDECLARE_SERIALYesYesYes

仅判断类型,用DECLARE_DYNAMIC足矣, 要动态创建对象,得用 DECLARE_DYNCREATE, 而如果要串行化,则得用 DECLARE_SERIAL宏.  这里只要求判断类型, 做法是: 每个从CObject派生的类都有一个CRuntimeClass对象与之关联,用CRuntimeClass的对象作为结点,组成一条链表,该链表就是侯俊杰先生说的"类别型录网",结点的指针从上面的CRuntimeClass成员列举中可以看到.当MFC中要确定对象的类型,过程为:

 用DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC两个宏生成对象对应的CRuntimeClass对象并插入到链表中,再通过宏RUNTIME_CLASS得到类的CRuntimeClass对象class***,然后使用CRuntimeClass的成员函数CreateObject创建一个该类的对象.

每个从CObject派生来的类都有一个CRuntimeClass* GetRuntimeClass( ) const函数,用来返回一个CRuntimeClass对象,如何在各个类之中插入CRuntimeClass对象,并且指定CRuntimeClass对象的内容及CRuntimeClass对象的链接?MFC用了两个宏实现了这些工作,即DECLARE_DYNAMIC(类名)和IMPLEMENT_DYNAMIC(类名,基类名)。

而CObject的另一个函数IsKindOf被用来进行运行时识别工作,自从有了IsKindOf,生活就变得美好了:

BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const

{

 CRuntimeClass* pClassThis = GetRuntimeClass();

 return pClassThis->IsDerivedFrom(pClass);

}

 而GetRuntimeClass()的作用是返回与该类关联的CRuntimeClass类指针:

CRuntimeClass* CObject::GetRuntimeClass() const

{

 return _RUNTIME_CLASS(CObject);

}

 而这个宏RUNTIME_CLASS 就是用来得到一个static const AFX_DATA CRuntimeClass classCObject.  (每个从CObject派生的类如CMainFrame都有成员classCMainFrame,这个class***成员就是与MFC类相关的那个CRuntimeClass对象!!).

 解决问题的关键还在于上面的IsDerivedFrom函数:

BOOL CRuntimeClass::IsDerivedFrom(const CRuntimeClass* pBaseClass) const

{

   const CRuntimeClass* pClassThis = this;

   while (pClassThis != NULL)

  {

   if (pClassThis == pBaseClass) return TRUE;         //从pBaseClass派生则返回,否则继续循环,万一不行,则返回FALSE;

   pClassThis = pClassThis->m_pBaseClass;

  }

   return FALSE; // walked to the top, no match

}

从宏观上说,IsKindOf函数判断了一个MFC对象(而不是类)的类型!!

结论: 如果你的类派生自CObject且使用了 DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC宏,那么,你的类就能够调用IsKindOf方法, 而如果调用IsKindOf方法, 则能够获取该类的类型信息(如果不调用IsKindOf函数,自然也就没从获取类型信息了).

RTTI并非专门用于动态创建,但可以用于动态创建, C++动态创建就是 char *p=new char(65) 这种! MFC中的动态创建主要是指CObject/CWnd/CCmdTarget/.../CMyView等的创建过程,用了CRuntimeClass,机制上与C++是不同的.

MFC的动态创建过程:

      1、定义一个不带参数的构造函数,因为我们是用CreateObject()动态创建,它只有一条语句就是return new XXX,不带任何参数。

   2、类说明中使用DECLARE_DYNCREATE(CLASSNMAE)宏;和在类的实现文件中使用IMPLEMENT_DYNCREATE(CLASSNAME,BASECLASS)宏,这个宏完成构造CRuntimeClass对象,并加入到链表中。

         3、使用时先通过宏RUNTIME_CLASS得到类的RunTime信息,然后使用CRuntimeClass的成员函数CreateObject创建一个该类的实例。

   4、CObject* pObject = pRuntimeClass->CreateObject();//完成动态创建

总结: MFC动态创建的最终动作是CRuntimeClass::CreateObject();获得类型信息只是过程的一环.

再简单说一下串行化,有一个有趣的事实:

#define _DECLARE_DYNCREATE(class_name)  _DECLARE_DYNAMIC(class_name)  static CObject* PASCAL CreateObject();

#define DECLARE_SERIAL(class_name) _DECLARE_DYNCREATE(class_name) AFX_API friend CArchive& AFXAPI operator>>(....);

 

 可简单地理解为 DECLARE_DYNCREATE 宏是 DECLARE_DYNAMIC 宏与 CreateObject函数的合, DECLARE_SERIAL 是 DECLARE_DYNCREATE  与操作符operator>>的合. 这里的operator>>是IMPLEMENT_SERIAL宏重载的.因此,MSDN中也看到了,要使一个类可串行化,使用DECLARE_SERIAL和IMPLEMENT_SERIAL宏只是一步而已,还要重载Serialize等工作.

 总结: 看CRuntimeClass的源代码知道,CRuntimeClass还有两个函数Load和Store,这两个函数的实现中还调用了CArchive的Read和Write方法,但是要说明的是,CRuntimeClass的Load/Store在文件的串行化中,只是做了一些辅助工作,如判断是否第一次出现,记录版本号,记录文件名等,真正实现串行化的还是CArchive的operator<</operator>>以及其他一些方法.

 CRuntimeClass对象只是MFC类的一个成员变量,它记录了本MFC类的一些信息,帮助本MFC类实现一些功能,仅此而已.

原创粉丝点击