C++知识文档十二_运行时类型信息RTTI

运行时类型信息的概念

我们要从面向对象的程序设计角度来理解运行时类型信息(Run-time Type Identification)的概念,面向对象程序设计涉及到一系列技术,这些技术基于类层次机制,提供可扩展性和可适应性。面向对象程序设计使用到的基本语言设施包括从一个类派生出另一个类的能力、虚拟函数以及用户自定义类型。这些特性使得程序员可以在不知道接口内部具体实现的情况下使用这个接口(这里说的“接口”即是指类,且通常是抽象类),并且可以在不影响原来的类的使用者的情况下,直接在原来的类之基础上建立新的类。举个例子来说:考虑一个简单的任务,其目标是通过某种用户接口系统获取来自用户的一个整型值,并将其传给应用程序。假设我们希望使应用程序独立于用户接口的实现细节,于是我们可以提供一个Ival_box类来作为交互的手段:

class Ival_box {

   public:

   virtualint get_value() = 0;   // 将数值取回应用程序

   virtualvoid prompt() = 0;     // 提示用户输入

   //…

};

显然,可能会有各种属于Ival_box型别的新型别出现:

class Ival_dial:public Ival_box { /*… */ };

class Ival_slider:public Ival_box {/* … */ };

//

这几个类之间的关系可以用下图表示:

这个应用层次(application hierarchy)独立于用户接口系统的实现细节。应用程序的编写独立于输入/输出的实现细节;在不影响应用层次的情况下,我们可以将应用程序加入到实现层次当中(implementation hierarchy):

虚线箭头代表着protected抽象类。protected抽象类是其派生类之实现的一部分,通用的用户代码无法对其进行访问。这种设计使得应用程序的代码独立于实现层次,实现层次的改动不会影响到应用程序代码。

出于现实因素的考虑,我在代码里的名称中使用了BB这个前缀;因为现今各主要程序库大凡都采用添加易识别的标志这样一种传统方式来增加可读性和易辨识性。更好的替代方案是使用namespace关键字。

将应用程序的类加入实现层次的类声明,其一般是像下面这样的:

ClassBB_ival_slider:public ival_slider, protected BB_slider {

   public:

   // 在这里,我们根据实现应用程序特定概念的需要,对Ival_slider的函数进行覆写

   protected:

   // 为了符合用户接口的标准,这里的函数覆写了BB_slider和BB_window的函数

   private:

   //这里是型别的表述和其它具体实现细节

};

这种结构通过覆写BB_window层次结构中的虚拟函数来表现用户接口系统要显示的细节内容。对一个用户接口系统而言,这也许并不是一种理想的组织结构,但好在这种结构并不常见。

派生类会继承其基类的属性。因此,派生有时候也被称为“继承”。当一种语言(比如C++)允许一个类直接拥有多个基类的时候,我们就说这种语言支持多重继承。


运行期型别识别(Run-timeType Identification)

在上面定义的Ival_box的一种可行的使用方法就是:在应用程序中将Ival_box对象转交给一个能控制屏幕的系统,并使该系统在屏幕出现任何变动的时候将对象交还给应用程序。这也正是很多用户接口的工作原理。然而,就像使用Ival_box的应用程序对用户接口系统一无所知一样,用户接口系统对我们的Ival_box也是一无所知的。我们以系统本身包含的类和对象为蓝本来定制系统接口,而不是以我们的应用程序中的类为蓝本。这是必要的,也是理所当然的。诚然这样的确也会造成一些不良的副作用,即丢失关于某些对象之类型的信息——这些对象先被传递给系统,之后又被返还回来,从而造成了丢失类型信息的情况。

要重新获得对象丢失了的类型信息,我们需要使这个对象能够体现自己类型。我们总是需要通过与某个对象之类型相匹配的指针或引用来对这个对象进行操作,因此要在运行期察看一个对象的型别,最先想到也最有用的方法就是施行一种类型转换操作,其在“对象之类型是预期的类型”时返回一个有效的指针,否则返回一个空指针(null pointer)。dynamic_cast运算符正是用来实现这个操作的。例如我们假设一个系统以指向BBwindow的指针作为参数来调用my_event_handler(),代码如下:

void my_event_handler(BBwindow* pw)

{

   if(Ival_box* pb = dynamic_cast<Ival_box*>(pw)) { //指针pw指向的是一个Ival_box型对象吗?

     int i = pb->get_value();

   //…

   }

   else{

    //噢欧,无法预料的事件

   }

}

可以这样来解释代码中发生的事情:dynamic_cast把用户接口系统所能理解的面向实现的语言“翻译”成了应用程序所能理解的语言。有很重要的一点是,在这里例子中,没有涉及到对象的真实型别。该对象是Ival_box的一种(比如Ival_slider),是由一种特定的BBwindow(比如BBslider)来实现的。在系统与应用程序的交互过程中,我们并不需要也不必要明确对象的具体型别。一个接口被用来代表一种交互过程中最重要的部分或全部细节。特别的,一个设计良好的接口还能够隐藏不重要的细节。

从基类映射(cast)到其派生类的过程通常被称为向下映射(downcast),因为这个转换过程在表示继承关系的树结构中显示了从上到下的移动方向。同样从派生类映射到其基类的过程被称为向上映射(upcast)。像BBwindow映射到Ival_box这样,从基类映射到其兄弟类的过程被称为交叉映射(crosscast)。

可见,RTTI是C++语言中的一个机制,该机制可以动态识别一个类型的真实的类型,并由此决定程序的运行方式。RTTI特性是和多态性对立的。多态性要我们将各种不同的东西当做相同的东西来处理,来提高程序的通用性。而RTTI是指得到了基类指针,有时想知道这个指针指向的具体类。由于之前的各种C/C++类库的厂家提供了RTTI功能,但不通用。所以标准C++鉴借各种RTTI实现的特点产生了标准的RTTI功能。

标准C++的RTTI有三个元素:

  typeid运算符

  type_info类

  dynamic_cast运算符


C++语言中的RTTI功能

如果使用VC编译器,在VC中打开RTTI功能要用/GR编译开关

Typeid(typedef sizeof)

C++提供了一种运算符:typeid

其参数可以是一个变量或对象或表达式,其返回类型是const type_info&.即一个编译器定义的对象type_info.的常引用。该对象没有公有的构造函数,没有公有的构造函数的类一般用来实现表示内部信息的类。不能有变量的情况。无法单独建立该类的对象,其定义如下:

class type_info {

public:

  //类的虚拟析构函数

  virtual ~type_info();

   //用于比较两个类型信息对象是否相等

  int operator==(const type_info& rhs) const;

  int operator!=(const type_info& rhs) const;

   //用于查询当前的类型信息类是否在另一个类rhs之前定义,即当前类型是否是rhs具体类

  int before(const type_info& rhs) const;

  //返加该类型信息对象所指对象的类型名称

  const char* name() const;

  const char* raw_name() const;

private:

  ...

};

 

该对象可以进行比较、取出类型名称、查询一个typeinfo是否在加一个之前定义等操作。

例子

其中Shape是一个基类,Circle是其具体类

Shape*ps = new Circle();

Circle* pc = new Circle();

//下面代码将显示class Shape*

cout<<typeid(ps).name()<<endl;

//下面条件返回false

if(typeid(ps).before(typeid(pc)))

{}

 

//比较47与int两个类型是否相同。结果是相等

if(Typeid(47) == Typeid(int)){}

inti

//比较变量i和类型int的类型是否相等,结果是相等

typeid(i)== Typeid(int)

//比较变量i的地址值与类型int*两个类型是否相等,结果是相等

typeid(&i)== Typeid(int*)    

当C++程序在进行typeid运算符操作时,可能会出错,会抛出一个bad_typeid异常类,该异常类的主要定义如下

classbad_typeid : public logic {

public:

    bad_typeid(const char * what_arg) :logic(what_arg) {}

    void raise()    { handle_raise(); throw *this; }

    // virtual __exString what() const;    //inherited

};

映射(cast)

标准C++的RTTI功能主要目的是有一个基类的对象,由于多态性的原因,它可能是一个具体类向上映射产生的,但在一些情况下我们不得不得到这具体类。这个过程就是C++的安全类型向下映射机制(downcast)。因为这个转换过程在表示继承关系的树结构中显示了从上到下的移动方向。同样从派生类映射到其基类的过程被称为向上映射(upcast)。像BBwindow映射到Ival_box这样,从基类映射到其兄弟类的过程被称为交叉映射(crosscast)

用标准C++如何进行映射:

  静态映射static_cast运算符

语法:

static_cast < type-id > ( expression )

type-id表示要映射的类型指针或类型引用,expression表示要操作的对象,可以是一个类型指针或一个类型左值。返回映射后的对象的指针或引用。在映射过程中,没有安全类型检查功能来保证将一个具体类的指针转为基类的指针一定成功,当无法映射时,会出错。所以这种映射是不安全的。只能在程序员可以保证的情况下可以使用。

例子

Circle类是基类Shape的具体类,Rect类是一个独立类

Shape* ps = new Circle();

Circle * pc = static_cast< Circle *>(ps); //正确

Rect* pr = static_cast< Rect*>(ps);     //

  动态映射dynamic_cast运算符

语法:

dynamic_cast < type-id > ( expression )

type-id表示要映射的类型指针或类型引用,expression表示要操作的对象,可以是一个类型指针或一个类型左值。返回映射后的对象的指针或引用。在映射过程中,有安全类型检查功能,当不能映射时回返空。

例子

class B { ... };

class C : public B { ... };

class D : public C { ... };

void f(D* pd){

   C*pc = dynamic_cast<C*>(pd);   // ok:C is a direct base class

   //pc points to C subobject of pd

   if (pc ==NULL){  cout<< “cast can not success”}

   else

   {

      pc->…

   }

   B* pb =dynamic_cast<B*>(pd);   // ok: B isan indirect base class

   //pb points to B subobject of pd

  ...

}

上述例子是一个向上映射的例子,可以映射到具体类的直接基类,也可以映射到接间基类,这相当于:指针在类的继承树上移动。

type-id也可以是一个void*,这时dynamic_cast会决定指针的真实类型,并返回其真实地址。

例子

重新解释一块内存:

class A { ... };

class B { ... };

void f()

{

  A* pa = new A;

  B* pb = new B;

  void* pv = dynamic_cast<void*>(pa);

   // pv now points to an object of type A

  ...

  pv = dynamic_cast<void*>(pb);

   // pv now points to an object of type B

}

使用dynamic_cast也可以实现向下映射,即通过一个基类指针转为其具体类的指针,dynamic_cast将在运行时检查参数expression基类指针的真实内容,检查expression是否指向其type-id的类型是真实对象,检查成功,返回该指针,否则返回空指针。

例子

class B { ... };

class D : public B { ... };

void f()

{

  B* pb = new D;               //unclear but ok

  B* pb2 = new B;

 

  D* pd = dynamic_cast<D*>(pb);     // ok: pb actually points to a D

  ...

  D* pd2 = dynamic_cast<D*>(pb2);  //error: pb2 points to a B, not a D

              // pd2 == NULL

  ...

}

常数映射const_cast运算符

语法:

   const_cast < type-id > ( expression )

type-id表示要映射的类型指针或类型引用,expression表示要操作的对象,可以是一个类型指针或一个类型左值。返回映射后的对象的指针或引用。用于将一个数据类型的指针或一个数据成员的指针显式转为相应的常指针。该映射也同时支持const、volatile、  __unaligned。

交叉映射reinterpret_castOperator运算符

语法:

   reinterpret_cast < type-id > (expression )

type-id表示要向下映射的类型指针或类型引用,expression表示要操作的对象,可以是一个类型指针或一个类型左值。返回映射后的对象的指针或引用。交叉映射可以任意类型的指针映射成其它任意类型的指针。但如何过多使用或滥用交叉映射,很容易产生不安全性。只有在一些底层编程中会使用到。

例子

将四个的字符重新解释为一个整数,在一些使用中是有意义的。

char  a[4];

int* p = reinterpret_cast < int* > (a);

reinterpret_cast可以将一个源类型的空指针值解释为一个目标类型的空指针值。


VC中MFC 的动态类型信息功能

VC的MFC的RTTI功能扩充了标准C++的RTTI功能。

MFC中基类CObject中的RTTI功能

CObject的虚函数IsKindOf可以判断一个变量是否属于指定的类型,含有该函数的类或对象是待判断的变量。该函数的参数是一个CRuntimeClass类,可以使用宏RUNTIME_CLASS从一个类产生其对应的CRuntimeClass类,类CRuntimeClass记录了一个类的运行时间信息的类型。

例子

CRuntimeClass* pClass = RUNTIME_CLASS( CObject );

得到类CObject的运行时间类的指针pClass,一般很少直接使用类CRuntimeClass来进行操作,只要将其传给函数IsKindOf中,就可判断变量是否属于指定的类型。使用例子参见实例1

CAge a(21); // Must useIMPLEMENT_DYNAMIC or IMPLEMENT_SERIAL

ASSERT( a.IsKindOf( RUNTIME_CLASS(CAge ) ) ); //判断对象a是否是Cage类型

ASSERT( a.IsKindOf( RUNTIME_CLASS(CObject ) ) ); //判断对象a是否是CObject类型

运行时间类型信息类CRuntimeClass

是MFC中提供RTTI功能的核心类,它实际是一个结构。该类的使用条件是你的类要从CObject派生,而且还要在你的类中包含以下至少一个宏:在类实现文件中至少包含一个动态宏 IMPLEMENT_DYNAMIC或动态建立宏IMPLEMENT_DYNCREATE或 序列化宏 IMPLEMENT_SERIAL,在类的头文件定义中要至少包含一个动态声明宏 DECLARE_DYNAMIC或动态建立声明宏DECLARE_DYNCREATE或 序列化声明宏 DECLARE_SERIAL

从一个从CObject中派生的类都可以关联一个其自己的CRuntimeClass类。可以通过宏RUNTIME_CLASS得到该结构,可以在运行时间根据一个对象,可得知所对应类型,并可以进一步实现以下用途,这些功能是标准C++所不支持的。

  对函数参数做一些额外的类型检查

  可以写出一些基于类的特殊的代码

CRuntimeClass类具体以下主要成员:

  LPCSTR m_lpszClassName

表示一个ASCII字串类名称

  int m_nObjectSize

   CRuntimeClass类所表示的对象的字节大小,如果对象没有数据成员,其值为零

  UINT m_wSchema

   对于有序列功能的对象,为变量分配一个值,如果不支持序列化,则该值曾-1

  CObject* ( PASCAL* m_pfnCreateObject )( )

   这是一个PASCAL调用规范的函数指针,该函数无参,返回一个CObject*,该函数指针用于指向变量默认构造函数,这一功能只能在类支持DECLARE_DYNCREATE 和IMPLEMENT_DYNCREATE宏时才有效,否则函数指针为NULL

  CRuntimeClass* ( PASCAL* m_pfn_GetBaseClass )( )

   这是一个PASCAL调用规范的函数指针,该函数返回变量的基类的CRuntimeClass*, 这只有在使用AFXDLL版的MFC类库时才有作用。

  CRuntimeClass* m_pBaseClass

   是一个变量的基类的CRuntimeClass*

  CObject* CreateObject( );

   可以按该类型的CRuntimeClass*,调用该函数来建立类型的实例。这就是MFC的动态建立对象的功能。但类型在支持动态建立宏:DECLARE_DYNCREATE 和IMPLEMENT_DYNCREATE宏

  BOOL IsDerivedFrom( const CRuntimeClass*pBaseClass) const;

   判当前类型是否从基类型CRuntimeClass* pBaseClass派生而来。是则返回TRUE,否则返回FALSE


实例分析

实例1

使用IsKindOf判断一个对象是否是指定的类型

  保证类支持MFC的RTTI功能

类必须从CObject基类的派生,并且要在类中定义

DECLARE_DYNAMIC 和IMPLEMENT_DYNAMIC宏或

DECLARE_DYNCREATE和 IMPLEMENT_DYNCREATE, 或者

定义DECLARE_SERIAL 和 IMPLEMENT_SERIAL 宏。

  用宏RUNTIME_CLASS产生的CRuntimeClass类指针调用函数IsKindOf

例子源代码:

// 在.H头文件中定义类

classCPerson : public CObject

{

    DECLARE_DYNAMIC( CPerson )

   public:

    CPerson(){};

    // other declaration

};

 

// 在类的实现文件中 .CPP 文件

IMPLEMENT_DYNAMIC(CPerson, CObject )

 

//使用该类的函数代码

voidSomeFunction(void)

{

   CObject* pMyObject = new CPerson;

   if(pMyObject->IsKindOf( RUNTIME_CLASS(CPerson ) ) )

   {

      //如果IsKindOf返回true, 可以完成向下映射

      CPerson* pmyPerson = (CPerson*) pMyObject;

      ...

      delete pmyPerson;

   }    

   ...

   delete pMyObject;

}

实例2

使用一个类的CRuntimeClass类来建立一个类。

  按实例1的要求建立一个类CStringX

  建立一个函数,该函数的参数是CRuntimeClass* 函数返回值是CStringX*

CStringX*GetStringX(CRuntimeClass* prtc)

{

   CObject* pobj = prtc->CreateObject();

   return dynamic_cast< CStringX*>( pobj);

}

实例源代码:

//类的头文件定义

class CStringX : public CObject

{

      DECLARE_DYNCREATE(CStringX)

public:

      void SetString(CString s);

      CString GetString();

      CStringX();          

      virtual ~CStringX();

      CString m_strText;

};

//类的实现文件

IMPLEMENT_DYNCREATE(CStringX,CObject)

CStringX::CStringX()

{

   m_strText = _T("");

}

CStringX::~CStringX()

{

}

CString CStringX::GetString()

{

   return m_strText;

}

void CStringX::SetString(CString s)

{

   m_strText = s;

}

//建立函数

CStringX*GetStringX(CRuntimeClass* prtc)

   {

      CObject* pobj = prtc->CreateObject();

      Return  dynamic_cast<CStringX*>( pobj);

}

//主函数

void main()

{

   CStringX* pstr = GetStringX(RUNTIME_CLASS(CStringX) );

  

}

 

从一个类的运行时间类出发,来建立一个的实例,是用MFC编译的重要技术。由于该技术使用CRuntimeClass将具体类分隔开了,降低了系统的耦合性,特别适用于建立通用框架代码,MFC框架类库就大量使用这种技术。

RTTI运行时类型信息是一种允许你在程序运行时决定一个对象类型的机制。RTTI被加入到C++是因为许多开发商都是自己实现该功能,这往往导致类库之间不兼容。显然,这使得语言级支持RTTI运行时类型信息功能非常重要。标准C++的RTTI有三个元素:typeid运算符、type_info类、dynamic_cast运算符。VC的MFC的RTTI功能扩充了标准C++的RTTI功能。CObject的虚函数IsKindOf可以判断一个变量是否属于指定的类型,含有该函数的类或对象是待判断的变量。运行时间类型信息类CRuntimeClass是MFC中提供RTTI功能的核心类。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值