返回首页
当前位置: 主页 > 讲师博文 >

探讨C++的运行时确定对象的类型(RTTI)

时间:2013-04-22 14:33来源:whhq 作者:whhq 点击:
Runtime Type Information,即运行时确定对象的类型,是C++语言和面向对象理论中的一个重用技术。通常情况下,具有继承树关系的一组对象,通过UPCAST,交由位于根的基类进行同一处理,这
  

作者:武汉华嵌技术部       姚老师

 

     Runtime Type Information,即运行时确定对象的类型,是C++语言和面向对象理论中的一个重用技术。通常情况下,具有继承树关系的一组对象,通过UPCAST,交由位于根的基类进行同一处理,这在设计Application Framework的时候,是至关重要的。

    这也带来了两个难以把握的技术难题:

   1是动态绑定,在C++中以虚函数的形式实现。

   2是运行时类型的识别,即RIIT。

   动态绑定的重要性不言而喻,很多书籍文章都在讨论。而RTTI同样重用,若忽视了,则会造成指鹿为马、指狗为猫的错误。

   可以通过下面的C++代码来描述:

#include <iostream>

using namespace std;

 class CAnimal

{

protected:

      char type[32];

};

 class CDog:public CAnimal

{

public:

      CDog()

      {

           strcpy(type, "Dog");

      }

};

 class CCat: public CAnimal

{

public:

      CCat()

      {

           strcpy(type, "Cat");

      }

     void CatchMouse()

      {

           cout << type << " Catch Mouse" << endl;

      }

};

 注意:

1) CatchMouse()方法是猫类独有的

2 )构造函数中,指定了对象的类型。

 接下来,创建一只狗,通过UPCAST到父类CAnimal ,然后强制DOWNCAST转化为猫,这样实现了指狗为猫,并且让狗拿耗子。代码如下:

int main()

{

      CAnimal * pa=new CDog;

      CCat * pc= (CCat * )pa;

      pc->CatchMouse();

      return 0;

}

让人惊奇的是,这段代码不但可以通过编译,还能运行出“狗拿耗子”这样荒谬的结果。

其他的面向对象语言,比如JAVA,决不让这样的程序运行,为什么C++反而可以呢?通过仔细研究发现,C++编译后,成员函数并不包含在对象当中,以下代码可以作证:

int main()

{

      cout << sizeof(CAnimal) << endl;

      cout << sizeof(CCat) << endl;

      cout << sizeof(CDog) << endl;

      return 0;

}

结果是

32

32

32

可见对象只包含数据成员,成员函数游离于对象之外。

 C++是静态语言,在编译时,确定函数的入口地址,因此由于指针的阴差阳错,误调用了猫类的CatchMouse()

 若将猫类的CatchMouse()声明为虚函数,则通过编译,但出现运行错误,代码如下:

 class CCat: public CAnimal

{

public:

      CCat()

      {

           strcpy(type, "Cat");

      }

 virtual void CatchMouse()

      {

           cout << type << " Catch Mouse" << endl;

      }

};

仔细观察,发现猫类对象的大小有所增加,代码和结果如下:

int main()

{

      cout << sizeof(CAnimal) << endl;

      cout << sizeof(CCat) << endl;   //36

      cout << sizeof(CDog) << endl;   //32

 

      return 0;

}

     增加的部分正好是VTable,而在堆内存中创建的是狗对象,尽管经过UPCAST和DOWNCAST被强行指为猫,但对内存中仍然是狗,并没有猫的VTable,因此出现运行错误。

    新版的C++标准,提供了typeid运算符,来实现RTTI功能,代码如下:

 int main()

{

      CAnimal * pa=new CDog;

      CCat * pc= (CCat * )pa;

 

      if(typeid(*(CAnimal *)pc) == typeid(CCat))

      {

           pc->CatchMouse();

      }

      else

      {

           cout << "Is Not a Cat" << endl;

      }

 

      return 0;

}

 注意:

1) 先要转为父类的指针。

2) VC6做以下设置:

Project->Settings->C++Language->Enable RTTI

 但早期的C++编译器,并不支持typeid运算符,因此一些历史悠久的类库,比如MFC,却采用了一些旁门左道的方法:将类别的信息放入链表中,然后一一对比,如图:

 首先准备一个结构体:

 struct CRuntimeClass

{

      // Attributes

      LPCSTR m_lpszClassName;

      int m_nObjectSize;

      UINT m_wSchema; // schema number of the loaded class

      CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class

      CRuntimeClass* m_pBaseClass;

      // CRuntimeClass objects linked together in simple list

      static CRuntimeClass* pFirstClass; // start of class list

      CRuntimeClass* m_pNextClass; // linked list of registered classes

}

然后以静态成员形式,塞入每个类当中:

位于根的基类:

// in header file

class CObject

{

public:

      virtual CRuntimeClass* GetRuntimeClass() const;

      ...

public:

      static CRuntimeClass classCObject;

};

 

// in implementation file

static char szCObject[] = "CObject";

 struct CRuntimeClass CObject::classCObject =

{ szCObject, sizeof(CObject), 0xffff, NULL, NULL };

 static AFX_CLASSINIT _init_CObject(&CObject::classCObject);

 

CRuntimeClass* CObject::GetRuntimeClass() const

{

      return &CObject::classCObject;

}

 以及派生类:

// in header file

class CView : public CWnd

{

public:

      static CRuntimeClass classCView; \

           virtual CRuntimeClass* GetRuntimeClass() const;

      ...

};

 // in implementation file

static char _lpszCView[] = "CView";

 CRuntimeClass CView::classCView = {

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

           &CWnd::classCWnd, NULL };

 

      static AFX_CLASSINIT _init_CView(&CView::classCView);

     CRuntimeClass* CView::GetRuntimeClass() const

      { return &CView::classCView; }

 

于是这样把每个对象的类型追踪出来。

void PrintAllClasses()

{

      CRuntimeClass* pClass;

      // just walk through the simple list of registered classes

      for (pClass = CRuntimeClass::pFirstClass; pClass != NULL;

      pClass = pClass->m_pNextClass)

      {

           cout << pClass->m_lpszClassName << "\n";

           cout << pClass->m_nObjectSize << "\n";

           cout << pClass->m_wSchema << "\n";

      }

}

 (本文为武汉华嵌嵌入式培训所创,http://www.embedhq.org  转载请注明来源)

 

相关链接

1)  C++双平台就业班

2) 华嵌成功实施邮科院虹信公司Linux下C++企业内训

------分隔线----------------------------

  • 李老师
  • 李老师
  • 胡老师
  • 胡老师
合作伙伴
  • 武汉工程大学合作培训机构

  • 国家信息技术紧缺人才培养工程(NITE)

  • ARM公司全球授权培训中心

  • 国内首家Symbian授权培训

  • 微软全球嵌入式合作伙伴

  • Altera全球合作培训机构