"); //-->
作者:武汉华嵌技术部 姚老师
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++企业内训
*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。