您的位置:首页 > 其它

RTTI运行时类型识别与类对象的动态创建

2018-02-01 08:31 405 查看
RTTI运行时类型识别

CRuntimeClass是MFC专用的。CRuntimeClass在文件AFX.H中声明,它是用来串起MFC从COBJECT继承下来的所有类。也可以把自己写的类加入这个链表。

struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName; //类名
int m_nObjectSize; //类对象大小
UINT m_wSchema; //分类编号(对不可分类的类,该值为-1)
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
CRuntimeClass* m_pBaseClass; //基类指针,但这里指针一定是指向父亲的,而不会指向祖父
// CRuntimeClass objects linked together in simple list
static CRuntimeClass* pFirstClass; // class list的链表头。注意这个与基类指针不同。并且,该对象是个静态变量,所有CRuntimeClass对象共享
CRuntimeClass* m_pNextClass; // 链表中紧跟当前对象的下一个对象。与当前对象不一定有继承关系
};


        特别注意:该struct使用了链表的概念,但是该链表与常规的数据结构链表是不太一样的。该链表每次新加入的节点都是放在链表头上的,类似栈。所以pFirstClass是随着每次新节点的加入一直在更新的,pFirstClass必然指向新加入的节点。而m_pNextClass则指向前一个加入的节点。
       如下图:

       首先初始化staticCRuntimeClass* pFirstClass为NULL。

①      最开始加入链表的是CObject。此时:

在_IMPLEMENT_RUNTIMECLASS宏中先直接设置m_pBaseClass:

<
4000
p>#m_pBaseClass= NULL;
然后在_IMPLEMENT_RUNTIMECLASS宏中调用AFX_CLASSINIT的构造函数设置m_pNextClass与pFirstClass:(这两者是先m_pNextClass后pFirstClass)

#m_pNextClass= pFirstClass =NULL;

#pFirstClass= CObject;

②      然后CCmdTarget加入链表。数据更新顺序同上:

#m_pBaseClass=CObject;

#m_pNextClass= pFirstClass= CObject;

#pFirstClass= CCmdTarget;

③      接着CWinThread加入链表。数据更新顺序同上:

#m_pBaseClass=CCmdTarget;

#m_pNextClass= pFirstClass= CCmdTarget;

#pFirstClass= CWinThread;

每一个类都拥有这样一个static的CRuntimeClass 成员变量。由于每个类都有static的CRuntimeClass 成员变量,所以每个类的对象都是引用以及修改本类的static的CRuntimeClass
成员变量,而不会将父类或者子类的修改掉。(父类与子类中的static同名成员变量是会造成“覆盖”的,该覆盖指的是引用时若不加域名直接使用,则使用的是本类的,而不会引用父类的。但实际上在内存中父类的static同名成员变量是依然存在的)

每个static的CRuntimeClass 成员变量都有一定的命名规则(在CRuntimeClass中采用的方法是在每个类的类名之前冠以"class" 作为它的名称,如CView的名称为classCView),然后,经由某种手段将整个类别库构造好之后,「类别型录」能呈现类似这样的风貌:

       注意是每个类共享一个CRuntimeClass成员变量。

       例:

       CCmdTargetcmd1,cmd2;

       CWinThreadthread;

       上面两个类定义了三个对象。其中:

①    cmd1与cmd2共享一个CRuntimeClass成员变量。所以cmd1与cmd2所拥有的CRuntimeClass成员变量是同一个。

②    thread单独使用一个CRuntimeClass成员变量。thread所使用的这个CRuntimeClass成员变量与cmd1、cmd2共享的那个CRuntimeClass成员变量不是同一个。

③    pFirstClass是CRuntimeClass结构体中的static变量,所以所有的对象都共享pFirstClass。因此,cmd1、cmd2、thread三者共享CRuntimeClass结构体中的static变量pFirstClass。

  

对于CView,其

CView.h

class CView : public CWnd

{

       DECLARE_DYNAMIC(CView)

}

CView.cpp

IMPLEMENT_DYNAMIC (CsdiTestView, CView)

 

其中的两个宏DECLARE_DYNAMIC与IMPLEMENT_DYNAMIC展开后,代码如下:

//*****************************************************************************

#define DECLARE_DYNAMIC(class_name) \

public: \

static CRuntimeClass class##class_name; \

virtual CRuntimeClass*GetRuntimeClass() const;

 

#define IMPLEMENT_DYNAMIC(class_name,base_class_name) \

_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF,NULL)

*****************************************************************************//

①    出现在宏定义之中的##,用来告诉编译器,把两个字符串系在一起。如果你这么使用此宏:

DECLARE_DYNAMIC(CView)

编译器前置处理器为你做出的码是:

public:

static CRuntimeClass classCView;//静态成员变量,所有该类对象都共享该变量

virtual CRuntimeClass*GetRuntimeClass() const;

②      IMPLEMENT_DYNAMIC宏之中又使用了一个_IMPLEMENT_RUNTIMECLASS宏,之所以这样做是因为_IMPLEMENT_RUNTIMECLASS宏在动态创建时还要用到。

_IMPLEMENT_RUNTIMECLASS宏定义:

//*****************************************************************************

#define _IMPLEMENT_RUNTIMECLASS(class_name,base_class_name,wSchema,pfnNew) \

static char _lpsz##class_name[] = #class_name; \

CRuntimeClass class_name::class##class_name = { \

_lpsz##class_name, sizeof(class_name),wSchema, pfnNew, \

RUNTIME_CLASS(base_class_name),
NULL }; \

static AFX_CLASSINIT_init_##class_name(&class_name::class##class_name); \

CRuntimeClass* class_name::GetRuntimeClass() const \

{ return&class_name::class##class_name; } \

*****************************************************************************//

③      其中又有RUNTIME_CLASS 宏,该宏用于取指定类的静态CRuntimeClass对象地址。由于在上面代码中调用该宏是RUNTIME_CLASS(base_class_name),NULL };所以传入的参数是base_class_name,从而获取的地址是基类(即父类)的静态CRuntimeClass对象地址。

其定义如下:

//*****************************************************************************

#define RUNTIME_CLASS(class_name) \

(&class_name::class##class_name)

*****************************************************************************//

④      看起来整个IMPLEMENT_DYNAMIC 内容好象只是指定初值,不然,其曼妙处在于它所使用的一个structAFX_CLASSINIT,定义如下:

//*****************************************************************************

struct AFX_CLASSINIT

{ AFX_CLASSINIT(CRuntimeClass*pNewClass); };

*****************************************************************************//

这表示它有一个构造函数(别惊讶,C++ 的struct 与class 都有构造式),定义如下:

AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClass* pNewClass)

{

pNewClass->m_pNextClass = CRuntimeClass::pFirstClass;

CRuntimeClass::pFirstClass = pNewClass;

}

很明显,此构造式负责linkedlist 的串接工作。即将所有对象

 

// in header file

class CView : publicCWnd

{

DECLARE_DYNAMIC(CView)

...

};

// in implementationfile

IMPLEMENT_DYNAMIC(CView, CWnd)

上述的码展开来成为:

// 头文件

class CView : publicCWnd

{

public:

static CRuntimeClass classCView; \

virtual CRuntimeClass*GetRuntimeClass() const;

//展开后定义了一个CRuntimeClass静态对象以及该对象的获取函数

//静态成员变量classCView,所有该类对象都共享该变量

//获取函数在这里仅仅是声明,具体实现需要在CPP中进行

...

};

// 实现

static char _lpszCView[] = "CView";//CRuntimeClass的第一个成员变量

//注意这里定义了一个static的字符串,下面传参传入的就是这个字符串。这样做会使得所有的该类成员名称都相同

CRuntimeClass CView::classCView = {

_lpszCView, sizeof(CView), 0xFFFF, NULL,

&CWnd::classCWnd, NULL };

//该行代码用于为定义的CRuntimeClass对象classCView赋值。因为CRuntimeClass是个结构体,所以可以用{}将所有的值包含在内并直接使用=来进行赋值。

//注意这种赋值方式仅仅会为结构体的常规成员变量赋值,而static变量与函数都是单独存储在另一片内存区的,所以不会被赋值。

static AFX_CLASSINIT _init_CView(&CView::classCView);

CRuntimeClass* CView::GetRuntimeClass() const

{ return &CView::classCView; }//对CRuntimeClass对象的获取函数进行实现

 

 

所有的MFC类,只要在类内调用上面的两个宏DECLARE_DYNAMIC与IMPLEMENT_DYNAMIC,即可将CRuntimeClass对象及其相关函数塞入类中。从而可以实现运行时类型识别的功能。

DECLARE_DYNAMIC宏必须配合IMPLEMENT_DYNAMIC宏来使用,而IMPLEMENT_DYNAMIC宏又必须传入当前类的基类。MFC所有的类都是从CObject派生而来,所以除CObject之外的类调用这两个宏是没问题的。但CObject是没有基类的,所以CObject直接调用这两个宏会有问题。

于是,CObject就不再调用这两个宏,而是改为单独进行CRuntimeClass对象及其相关函数的实现,直接将代码写在了CObject类的.h文件中。

同时,由于CRuntimeClass内部的pFirstClass变量是static类型,CObject是第一个使用CRuntimeClass对象的类,所以在CObject所在的头文件需要对pFirstClass进行初值的赋值,MFC中将其赋值为NULL。

 

编译过程中:

比如定义了一个CWinApp对象、一个CFrameWnd对象、一个CDocument对象、一个CView对象。

①    首先编译CWinApp对象。检查CWinApp对象是否有基类;若有,则检查其基类是否编译过;若没有编译过,则先编译其基类;然后对其基类进行检测,检测其是否有基类……

不断重复上述步骤,直到检测到CObject为止。于是,就出现了这样的编译顺序:

        CObject->CCmdTarget->CWinThread->CWinApp

②    然后编译CFrameWnd对象。检测CFrameWnd的基类,发现是CWnd,没有编译过;检测CWnd的基类,发现是CCmdTarget,CCmdTarget已经编译过了,故而不再编译。于是其编译顺序为:

       CWnd->CFrameWnd

③    接着编译CDocument对象。其基类是CCmdTarget,已经编译过了,故而编译顺序为:

       CDocument

④    最后编译CView对象。其基类是CWnd,已经编译过了,故而编译顺序为:

       CView

如下图:



有了上面这张网,就可以实现RTTI的能力。

由于CObject是所有类的基类,因此在CObject中定义一个函数IsKindOf(),以便于所有类继承:该函数只需要在CObject中定义

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

{     

    CRuntimeClass* pClassThis=GetRuntimeClass();     

    while(pClassThis != NULL
bf7f
)     

    {     

        if(pClassThis==pClass)     

            return TRUE;     

        pClassThis=pClassThis->m_pBaseClass;     

    }     

    return FALSE;     

}

       当对某个类调用了IsKindOf()函数时,IsKindOf()函数就会判断当前类的CRuntimeClass对象与参数指定类的CRuntimeClass对象是否相等。

①    若相等,说明当前类就是指定类,也就是当前类与指定类是同一个类,返回TRUE。

②    若不相等,那就判断当前类的基类CRuntimeClass对象与指定类的CRuntimeClass对象是否相等,若相等,说明当前类的基类与指定类是同一个类,从而当前类是指定类的派生类,返回TRUE。

③    若依然不相等,继续重复步骤②

④    如果遍历到CObject依然与指定类的CRuntimeClass对象不相等,那么说明当前类既不是指定类也不是指定类的派生类,返回FALSE。

使用方法:

如有1个对象:

CMyDoc* pMyDoc;

则:

//判断pMyDoc是否属于CMyDoc,返回TRUE

pMyDoc-> IsKindOf(RUNTIME_CLASS (CMyDoc));

 

//判断pMyDoc是否属于CObject,返回TRUE

pMyDoc-> IsKindOf(RUNTIME_CLASS (CObject));

 

//判断pMyDoc是否属于CView,返回FALSE

pMyDoc-> IsKindOf(RUNTIME_CLASS (CView));

  

以上是为了实现在运行时可以识别某个类型。要做到动态创建,还需要在CRuntimeClass的struct中加入:

struct CRuntimeClass

{

       ……

 CObject*CreateObject();

 staticCRuntimeClass* PASCAL    Load();

}

       两个函数的实现:

CObject* CRuntimeClass::CreateObject()//该函数用于创建一个当前类的对象

{

 if(m_pfnCreateObject==NULL)return NULL;

 CObject*pObject;

 pObject=(*m_pfnCreateObject)();//函数指针调用

 returnpObject;                                  

}

CRuntimeClass * PASCAL
CRuntimeClass::Load()//该函数用于匹配输入的类名,并返回一个指向该类的CRuntimeClass指针。否则返回NULL

{

 charszClassXXX[64];

 CRuntimeClass*pClass;

 cin>>szClassXXX; //假定这是我们希望动态创建的类名,这里用手动输入

 for(pClass=pFirstClass;pClass!=NULL;pClass=pClass->m_pNextClass)

 {

        if(strcmp(szClassXXX,pClass->m_lpszClassName)==0)

        return pClass;

 }

       return NULL;

}

 

同时要添加两个宏DECLARE_DYNCREATE与IMPLEMENT_DYNCREATE:

#define DECLARE_DYNCREATE(class_name)\
       DECLARE_DYNAMIC(class_name) \
       static CObject* PASCAL CreateObject();

#define IMPLEMENT_DYNCREATE(class_name,base_class_name) \
       CObject*PASCAL class_name::CreateObject() \
              {return new class_name; } \
       IMPLEMENT_RUNTIMECLASS(class_name, base_class_name,0xFFFF, \
              class_name::CreateObject,NULL)

可见DECLARE_DYNCREATE与IMPLEMENT_DYNCREATE这两个宏包含了RTTI所需要的两个宏DECLARE_DYNAMIC与IMPLEMENT_RUNTIMECLASS。因此,具备动态创建能力的类,必然具备RTTI能力。

但是并非所有的MFC类都用动态创建宏替换了RTTI宏。只有替换掉的才能动态创建,未替换掉的不能动态创建。

例如:

①    CObject中两种宏都没有使用,所以CObject不具备动态创建能力。

②    CCmdTarget、CWinThread、CWinApp、CDocument、CView都使用了RTTI宏,而没有使用动态创建宏,所以这些类具备RTTI能力,但却不能动态创建。

③    CWnd、CFrameWnd使用了动态创建宏,所以这两个类即能动态创建也具备RTTI能力。

由此可知:子类与父类的动态创建能力是没有关系的。可能子类具有动态创建能力而父类没有;也可能父类具有动态创建能力而子类没有。这要看具体父类与子类中各自调用的宏是哪一种。

 

仿真代码:

void main()
{
CRuntimeClass* pClassRef;
CObject* pOb;
while (1)
{
//首先根据输入的类名,获取一个该类的CRuntimeClass指针
//若获取不到则break
if((pClassRef = CRuntimeClass::Load()) == NULL)
break;

//然后调用上面CRuntimeClass指针的CreateObject()函数来创建对象
pOb = pClassRef->CreateObject();
if(pOb !=NULL)
pOb->SayHello();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: