RTTI(Runtime Type Identification):运行时类型识别,可以帮助我们在程序运行时知道某个对象是属于哪个类别。
一、使用RTTI,需要注意三个地方:
1、编译时需选择/GR选项(/GR的意思是enable C++ RTTI)
2、包含typeinfo.h
3、新的typeid运算子。typeid的参数可以是类型别名,也可以是对象指针。它传回一个typeinf&。
type_info是一个类,定义于typeinfo.h中:
<span style="font-size:14px;">class type_info { public: virtual ~type_info(); int operator==(const type_info& rhs) const; int operator!=(const type_info& rhs) const; int before(const typ_info& rhs) const; const char* name() const; const char* raw_name() const; private: ... };</span>
二、RTTI的实现原理
如果你有一个产品,你想得到产品型号,那么你就要去查找型号对照表。同理如果要在运行时知道某个类的型号,就需要为类添加额外的信息,来记录类的型别信息。
1、MFC中每个类都有一个CRuntimeClass成员变量,充当此额外的记录结构。
CRuntimeClass对象中与RTTI相关的内容:
<span style="font-size:14px;">struct CRuntimeClass { LPCSTR m_lpszClassName; int m_nObjectSize; UINT m_wSchema; CObject* (PASCAL* m_pfnCreateObject)(); CRuntimeClass* m_pBaseClass; static CRuntimeClass* pFirstClass; CRuntimeClass* m_pNxtClass;
CObject* CreateObject(); }</span>
2、DECLARE_DYNAMIC / IMPLEMENT_DYNAMI
(1)、为了将CRuntimeClass对象塞到类中,并声明一个可以抓到该对象地址的函数,MFC定义了DECLARE_DYNAMIC宏:
#define DECLARE_DYNAMIC(class_name) \
public: \
static CRuntimeClass class##class_name;\
virtual CRuntimeClass* GetRuntimeClass() const;
注:宏中的##是告诉编译器将左右的字符串连接在一起。
如:DECLARE_DYNAMIC(CView)
编译器前置处理器编译后的代码为:
public:
static CRuntimeClass classCView;
virtual CRuntimeClass* GetRuntimeClass() const;
(2)、为了将类别记录(各个CRunTimeClass对象)的内容指定以及串接工作最好也嵌入到类中,MFC定义了IMPLEMENT_DYNAMIC宏:
#define IMPLEMENT_DYNAMIC(class_name, base_class_name) \
_IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, 0xFFFF, NULL)
_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(class_name)宏定义为:
#define RUNTIE_CLASS(class_name) \
(&class_name::class##class_name)
其中:AFX_CLASSINIT是一个结构体,含有一个构造函数,定义如下:
struct AFX_CLASSINIT
{
AFX_CLASSINIT(CRuntimeClass* pNewClass);
}
构造函数定义如下:
AFX_CLASSINIT::AFX_CLASSINIT(CRuntimeClss* pNewClass)
{
pNewClass ->m_pNextClass = CRuntimeClass::pFirstClass;
CRuntimeClass::pFirstClass = pNewClass;
}
很明显,该构造函数负责类别信息的串接。
实例:
// in header file
class CView : public CWnd
{
DECLARE_DYNAMIC(CView)
...
};
//in implementation file
IMPLEMENT_DYNAMIC(CView, CWnd)
上述代码宏展开后是:
// 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; }
(3)、注意串行的头部需要特殊处理,所以在根类别CObject中,不能套用现成的宏DECLARE_DYNAMIC和
IMPLEMENT_DYNAMIC,必须特别设计如下:
// 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 implementation file CRuntimeClass* CRuntimeClass::pFirstClass= NULL;
(4)、关于AFX_CLASSINIT结构体
关于RTTI宏的实现实际上是没有必要了解的,只要会用,知道是怎么一回事就行了。但是有些设计思想、设计方法却是可以借鉴的。
AFX_CLASSINIT结构体没有数据成员,只有一个带一个参数的构造函数,在用的时候只是定义一个全局变量(且不会在后续代码中使用它), 它的价值就在于它的构造器,因为在构造器中实现了类别串接。我们发现实际需求是进行“串接”这件事,但是解决方法是“定义一个变量”,在这之前我还没遇到过这种情况,写一个全局函数不就行了吗?为什么要这样做?我想这样做应该是考虑到C++的封装性。
(5)、RTTI既是“串接图”,也是“继承图”
“串接”的意思是将源文件所有声明和实现的类串接成一个链表,串接次序:头文件中类的定义次序。(所以总体上是基类在前,派生类在后,继承自同一父类的子类的串接次序是先定义的先串接)。
"继承图"毫无疑问是标记类的继承层次。
RTTI中既标记了类的串接次序,又标记了类的继承层次,在CRuntimeClass结构体中成员CRuntimeClass* m_pBaseclass起串接类继承层次的作用,成员CRuntimeClass* m_pNextClass起串接类的串接次序。
3、DECLARE_DYNCREATE / IMPLEMENT_DYNCREATE
动态生成:在程序运行期间,根据动态获得的一个类别(通常来自读文件),要求程序产生一个对象
有了上述的类型识别,动态生成技术就简单了:根据输入的类别,从类别型录网中查找对应的元素,然后调用记录的构造函数,产生对象。
注:CRuntimeClass中的函数指针m_pfnCreateObject和成员方法CreateObject()。
(1)、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)
可见,1*、DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏中包含DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC宏。
2*、CreateObject内部实际上是new了一个对象,注意动态创建的对象,在使用完成后要手动调用delete删除。
3*、在MFC应用程序中,CWnd、CFrameWnd、CXXXWnd、CXXXFrameWnd、CXXXDoc、CXXXView都是使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE宏,其余的使用DECLARE_DYNAMIC和IMPLEMENT_DYNAMIC宏。
三、RTTI的使用
(1)、类型识别,IsKindOf()
在CObject中添加IsKindOf成员方法,就可以将参数指定的CRuntimeClass对象与型别录中的元素一一比对,比对成功返回true,比对失败返回false。
<span style="font-size:14px;">BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const { CRuntimeClass* pClassThis = GetRuntimeClass(); while(pClassThis != NULL) { if(pClassThis == pClass) return TRUE; pClassThis = pClassThis->m_pBaseClass; } return FALSE; }</span>
注意:IsKindOf中追踪的是m_pBaseClass。因此可以判断出一个类是否是某种基类型的。
例:CView* pView = new CView;
pView ->IsKindOf(RUNTIME_CLASS(CWinApp));//注意这里的输入参数是CWinApp类静态成员classCWinApp的地址。
函数IsKindOf的执行过程是按照CView、CWnd、CCmdTarget、CObject的循线路径,每获得一个CRuntimeClass对象指针,就拿来和"&CView::classCView"指针比对。直到最顶端的CRuntimeClass对象的m_pBaseClass成员为NULL;返回结果。
(2)、动态生成对象
动态生成一个CView对象:
CObject* pObject = RUNTIME_CLASS(CView)->CreateObject();
删除该对象:
if(pObject) {delete pObject; pObject = NULL;}