C++支持的RTTI

                                                           C++支持的RTTI

                                                      周顺利翻译                                             


    标准C++提供标识符typeid()来得到类型信息。它的参数是一个表达式(一个对象的引用或者指针)或者一个类型名。它返回一个类型信息的常量引用,包含一些类型信息。

 类型信息类有很少几个函数:
 const char *name(): 用来得到类型标识符字符串(比如,int,MyClass).
 bool before(osnt type_info&): 用来排序。
 operator==()和operator!=():用来比较对象类型。
这些信息并不能够帮助找出对象之间的关系并且不能解决出现在程序中的大部分问题。因为C++的RTTI并不是为这些应用设计的。不同的程序有不同的需求并且统一的标准的类型描述不能满足所有的需求。
 然而,type_info类可以被用来存储更加详细的类型信息。这样每一个程序可以定义和使用它自己的RTTI系统。一个很灵活的解决方案。问题是必须有人定义RTTI记录结构并映射每一个类型记录。这并不是一个小事,有一些代码是每一个程序都得写。
 每一个程序的的RTTI有多大的不同呢?它们的不同之处和相同之处是什么呢?我们应该怎样定义自己的RTTI系统。我们应该在什么情况和怎样使用它。这些问题将分三部分讨论。
 这篇文章的第一部分讨论两个典型的程序的RTTI系统。它收集和分类通用的RTTI系统的需求。
 这篇文章的第二部分讨论怎样实现一个满足这样需求的RTTI系统。C++语言可能是最强大的编程语言。这样一个RTTI系统在其它编程语言中是不能使用的。这篇文章很好的展示了C++的强大。这个系统中用到了很多高级编程技巧和设计模式。因此即使你对RTTI并不是很感兴趣你也会发现这个系统很有趣。
 这篇文章的第三部分讨论了一些更加深入的持久化观点。讨论了一个使用展示的RTTI系统的Stream类库。这个解决方案的模块性和灵活性在这篇文章的最后进行了讨论。
RTTI程序
 有许多的RTTI程序,但是持久化和程序生成器是大家十分熟悉的两个。他们可能是最困难的程序和最需要RTTI系统的程序。
持久化
 持久化的基本任务是很简单的。程序和一些重要的程序数据是以一些相互引用的对象来组织的。持久化意味着对象被保存到永久存储--文件--,并且在稍后程序可以从文件中重新载入原来的状态。更准确的说,一个程序或者数据只要它的生存周期超过程序运行时间,则它们是持久的。也就是说程序运行结束后,这些数据还可以访问,则这些数据就是持久的。
 当程序读取文件的时候,对象的类型被从文件中读出,一个新的对象被创建,并用从文件中读取的数据初始化这个新对象。这就是为什么我们需要RTTI。保存和载入对象的基本过程看起来十分简单。但是如果你考虑细节和健壮性,这将变的十分复杂。
 每一种数据类型的值都要在他们的内部表示(二进制)和文件表示(文本或者二进制)之间进行转换。谁来负责这些转换。通常来说,每一个Stream对象都有函数或者方法用来对流对象中写入和读取每一种数据类型。这些函数或者操作符对每一个类型都有一个重载的版本,,程序用来保存和载入数据。这是用重载函数实现的RTTI系统。但是这并不能解决创建一个特定类型的对象这个问题。我们可以假定这些对象已经存在并且流对象只需要填充相应的变量。
 当所有的数据被载入,所有的对象都被创建。我们才完成了一半的工作。有些对象可能含有不值得保存的数据(比如可以从其他数据计算出的数据),。这些对象只需要被更新就行了。对象之间的相互引用也要被更新,因为这些对在被更新的时候被载入了不同的地址空间。
 最后,我们来考虑错误处理。如果流对象发生错误会怎么样?,比如,一个值丢失了会怎么样,或者值的顺序发生改变又会怎么样?这是一个很常见的错误,因为不同版本的程序有不同的数据结构。不同版本的程序之间的文件的兼容性也是必须的:不同的版本的应该可以从以前版本保存的文件中读取数据。更甚的是,如果旧版本的程序可以从新版本创建的文件中读取数据,作一些修改保存,并不会丢失数据信息将会更好。旧版本不能处理和理解新版本的类型和特征,但是他们可以保存这些数据到某个地方并且不需要知道任何关于他们的意义。
 市场上有很多的解决方案和类库可以用来做持久化。但是他们都之提供基本的需求。他们可以保存和载入对象,但是他们不具备容错功能。有两种比较常用的解决方案:
 对象具有用来保存和载入对象的虚函数。它参数是一个流对象的引用。流对象有重载的操作符和函数用来写入和读取基类的值。变量是如何被载入和保存这是程序的责任。比如,MFC(Microsoft Foundation Class Library)提供这样方式的持久化:
 有些类库保存和载入对的内存映象到二进制文件。这种解决方案只需要编很少的程序。但是必须使用一点小技巧用来验证内存地址和虚函数表。要读取和编辑数据文件是不可能的。任何数据文件的损坏都将导致严重的问题。另外一个缺点是所有的变量都必须被保存。我们没有办法区别持久数据和暂时数据。
 作者没有见过任何一种C++持久化解决方案,同时提供可以修改的数据流,足够健壮和容错性够强的流结构。这里我们值得提一下的是其他一些编程语言和系统实现了持久化,如:CORBA,COM/OLE,Java,C#,Delphi/C++ Builder.
对于这些系统的详细讨论和比较超出了这篇文章的讨论范围。他们都有他们的好处和不足之处。就我所知,它们都没有提供足够的灵活性。

持久性程序:保存和载入程序的数据,保存文件编辑器里边的数据是一个典型的例子。

分布式程序:当程序的不同部分在不同的机器上运行时,数据对象必须通过网络进行传输。在传输对象之前,对象得被打包到消息当中去。当消息被发送以后,被发送出去的数据必须被在目标机器上重新构造。

保存和载入程序的状态: 最终用户需要继续他们以前没有完成而停止的工作。因此当用户退出的时候,程序需要保存相关的信息。并且在下一次启动的时候恢复到以前的状态。现在,这个问题通常通过将变量写入INI文件或者注册表来解决。使用持久化是一个比较优雅和简单的解决方案。

配置程序:程序的基本功能可以通过配置文件来定义。当程序启动,持久对象从配置文件中载入。这些对象决定了程序
是什么样和可以作些什么。不同的配置文件可能生成出不同程序。这就说到下一章,一种特殊的程序——程序生成器用来生成不同的配置文件。

程序生成器: 程序生成器是什么?有很多的程序生成器,但是具体定义它是一个什么东西。让我们来讨论对我们比较重要的特征。程序生成器是程序开发工具,使得程序开发工程更加快,更加简单。他们提供一套组件和一些精美的图形用户界面,其他人可以通过增加和配置组件开发程序。这种系统的优点是开发者不需要高深的编程知识并且程序可以很快的开发出来。从另一方面来说,这种系统有两个缺点:程序的功能有限和性能不高。性能不高意味着同样用C++开发的程序比用俎件开发的程序运行的快的多,因为组件内部的通信机制不高。或者是开发者不能使用更加有效的数据结构。现在这不是一个很大的问题。计算机的运行速度在不断的增加,并且最重要的资源是软件开发人员的时间。但是在有些程序(嵌入式,数据采集和控制系统),速度仍然是十分重要的。能力上的限制是最大的障碍。所有的程序生成器都是针对特定的领域设计的,比如数据库程序,数据采集程序或者图形用户界面。如果程序需要不存在的组件,这时使用组件开发人员将会有很大的麻烦。有时,这个问题可以通过一些方法解决,或者系统提供编写组件的方法,但是这些方法破坏了原先的优点。最糟糕的是开发一个大的程序通常不可预测。开始程序生成器好象可以满足所有的要求,但是当程序基本完成的时候,开发者发现一个最重要的功能没办法实现——仅仅因为一个组件缺失或者它的行为不如预测的那样。
 虽然存在以上的缺点程序生成器还是十分流行的。其中一个原因是它提供最可靠的程序开发方法。程序开发人员可以在一个更高的抽象层次工作并且他们不用关心组件的实现细节。这些组件都是专家开发的并且经过严格测试。
 最好的程序生成器合并了以上两种方法的优点。让我们来想象这样一种情形:一个系统的组件使用C++开发,程序生成器是一个使用图形代表对象,增加新对象,设置他们的属性。有些属性提供对象之间的连接点。其他的决定和描述对象的行为和界面。程序生成器用户只能使用组件在抽象层工作,然而小队 C++开发者可以开发需要的组件。C++ RTTI系统可以用来构造程序生成器。运行时系统的对象都有一些详细的RTTI信息,程序正是由这些组件构成的。另外一个程序,程序生成器可以访问这些组件。它可以知道运行时对象的层次信息,新对象和他们的属性。一个好看的图形用户界面用起来很方便,可以完全控制的组件可以很高效的开发程序。

 程序生成器和运行时系统有两种通信方法:程序生成器有同样一套组件并且可以构件一套单独的类库。当程序开发好了以后,它被保存到一个文件,运行时系统将装载程序。当有新的组件增加,这种解决方案需要重新编译程序生成器。运行时系统为程序生成器提供访问内部动向系统的接口。这使得开发商业程序同时保持组件是私有的。这个接口是十分复杂的,必须提供一种机制,当程序生成器改变程序结构的时候持久化程序。程序生成器用持久化来保存和载入组件。因此,两种程序同时它需要以上所有的需求。程序生成器需要更高级的特性:更多的冗余的属性。程序生成器需要冗余的属性为用户提供更高级的特性。只有一小部分属性必须被保存到数据流。比如,一个长方形有六个不同的属性:四个角的坐标和长和高,总共10个数据,但是他们不是独立的。只需要4个既可以计算出其他的数据 。
 程序生成器必须为用户提供一个组件选择器。因此,它必须检查所有可以使用的组件,得到可以使用的类型信息,显示这些信息。还有一种特别的组件,叫做容器用来装载其它组件。程序生成器必须可以处理组件继承。
 
需求:
上边从RTTI的角度讨论了持久化和程序生成器。RTTI的需求和关系也进行了一些讨论。持久化和程序生成器都需要RTTI的支持。下边就讨论这些需求。简短来说需求如下:
 RTTI系统需要为持久化和程序生成器提供足够的信息。必须很容易的增加RTTI到C++的类型系统。RTTI必须可以描述C++的特性,包括多继承,多态,抽象类,和模板。
 RTTI必须提供使用类库的方法。最复杂的是讨论如何使用标准容器。编译器提供RTTI可能是最简单的方法。但是标准C++并没有提供这方面的标准。不同的程序需要不同的RTTI系统,这些不同的需求不可能用同一个C++标准来包括。C++可以提供一个自己提供的类库来支持RTTI系统,这种解决方案是十分的灵活。

类型
RTTI系统必须可以描述用户定义的类型和系统内建的类型,包括类库里边定义的类和程序当中使用的类型,和程序自己定义的类型。这些类型可以分成三种:
基本类型
复合类型
容器类型

基本类型
基本类型和内建类型并不是相同的。内建类型一定是基本类型。基本类型不一定是内建 类型。比如字符串最好是解释成基本类型。最重要的是基本类型是RTTI系统的原子性的元素。基本类型不可以使用其他类型来描述,相反,可以使用基本类型来复合描述自定义的类型。

复合类型
C++的结构和类都是复合类型。基本类型和复合类型的区别是编译器必须描述复合类型的成员。用来描述颜色的类是一个比较简单的例子。Color类有三个数字来描述。

容器类型
容器类型需要特别的注意。容器类是一个特殊的类型用来容纳对象。STL的容器(vector,list,set,map)和数组是最好的例子。另外,RTTI必须可以:
插入元素到容器当中
从容器当中删除元素
迭代容器当中的元素
容器类类型描述器的定义必须和复合类型的描述器一样简单。

接口
上边提到的所有虽然不同,但是它们要在一个类库中提供。RTTI系统必须提供一致的接口访问类型信息和类库的类数目。这些接口必须和实际的类型信息和对象的数据结构独立,必须可以描述所有的C++特性。综合以上,RTTI必须很容易使用。RTTI必须有一个接口用来得到类型描述信息,就象标准C++中的typeid()操作符。比如,一个GetTypeInfo()函数返回给定类型描述地址。所有的基本类型,复合类型和容器类型只有一个描述符,用来访问RTTI系统保存的类型信息。
即使每一种类型都有一个类型描述记录,复合类型和容器类型必须保存额外的关于它们成员信息和另外一个用来迭代它们的成员树。这里可以使用迭代器。属性迭代器可以用来迭代对象层次,迭代器的实现是完全透明的。

RTTI系统接口确保程序生成器和持久化流库不需要知道C++类的任何信息,它们可以通过RTTI接口得到必要的信息和通过属性迭代器得到类库信息。
RTTI包括三个部分:
基本类型的RTTI描述
复合类型和容器类型的RTTI性质描述符
属性迭代器
下边讨论这些部分:
基本类型的RTTI的描述
所有的类型包括基本类型,复合类型,和容器类型必须有基本类型的描述。这是一个Static对象——Type Info record。每一个类型都有一个Type Info record。Type Info record 是Type Info classes的实例。每一个类型都有一个Type Info record和这个类的一个实例。Type Info 类是自动由基类自动生成的。Type Info record提供以下信息:
Type name:可以识别的类型名。
Type Identifier:一个独一无二二进制的类型描述符。
Size: 返回类的大小的函数。
Creating objects:创建一个对象和对象数组的函数。
Destroy objects:销毁一个对象或者一组对象的函数。
访问对象值。用来访问 对象的值函数(e.g. GetVal(),SetVal())
用来得到类型信息的函数:
是复合类型吗?
是容器类型吗?
是抽象类型吗?
用来遍历Type Info record的函数:
Type Info record用来得到所有类型的信息。它是RTTI的基本,如果程序需要在运行时访问类型信息它也是必须的。

复合类型和容器类型的描述:
复合类型和容器类型需要额外类型信息,除了Type Info record。他们不是简单类型,可以直接使用GetValue()和SetValue()函数来访问值。他们包含一系列对象并且RTTI系统提供这些对象的信息。复合类型包含成员,容器类型包含元素。成员类型和元素类型可能也不相同,容器类型的元素是多态对象的指针。成员和元素叫做性质。
并不是所有类的成员变量都是性质,不光成员变量,成员函数也可以是性质。复合成员的的性质的个数是确定的,但是对于所有的容器类型决定于存储在元素的个数。因此,RTTI系统不可能依靠性质的个数来保持一致,必须遍历他们。
成员描述可以通过一组属性描述——Property Descriptor Table来实现。属性描述符是用简单数据记录描述符合记录类型。成员可以是基本类型,复合类型,和容器类型。属性描述符有几个子类型,取决于它所包含的属性的类型,但是它包含以下公共信息:
访问Type Info record
存储人可以识别的独一无二的属性名。
提供一个二进制属性标志符。
保存一些标志来描述属性:
它是不是一个对象的引用或者指针。
这个属性是否是指针或者引用的对象。(如果属性是指针且指针拥有对象,当它被另外的对象替换的时候对象删除。)
元素的性质的个数可以扩展吗?(如果是一个容器,是Public protected private readable writable)

必须提供用来得到存储成员元素的内存地址的机制。这可以是成员变量的offset或者函数的入口地址。

创建一个属性的迭代器。
用来取得和设置属性的函数
增加新的扩展属性的函数。
Property Descriptor 是很有技巧的。它隐藏了不同成员的差别并且提供一个公共的接口来访问所有可能的属性。

属性迭代器
Type Info record 和Property Descriptor Table 保存了所有的RTTI信息。Property Iterators 并不增加新的信息。他们只是提供一个很好用的接口。Property Iterators可以用来指向任何类库中的类,Iterators可以遍历所有的类。,复合类型和容器类型可以创建新的iterator用来访问他们的属性。新的iterator 打开属性树。Property Iterators的成员函数可以访问类型信息的的信息和服务和Property Descriptor records.

 


如何使用RTTI

持久化
实现了RTTI的类库叫做属性库因为它实现C++的对象属性。系统的一个新的组建,一个流类库,用来执行持久化。流类库用来负责处理数据流和文件。

这个解决方案一个优点是 Property Library(the RTTI system)和Stream Library(the data stream)和程序相互独立。使用Property Library接口,Stream Library可以保存和载入C++对象而不用知道他们的类型。流的格式只和Stream Library相关。它可能是文本,XML或者二进制格式,所以程序选择流的格式。改变文件格式不需要改变源程序的类,RTTI描述他们,而不是Property Library.

保存对象
首先,属性迭代器指向创建的对象的层次的开始,这个地址传送给Stream Library 的Save()函数。Stream Library 不知道对象信息,但是可以使用属性迭代器来得到必须的信息。Save()函数在属性和之间遍历,取决于这些类型是否是基本类型。如果是基本类型,类型名,类型,和值被写入流中。如果是复合类型或者容器类型,属性值写入流,并且一个对象值的新块。文本流看起来象C语言:
 obj1=
{
int a=23;
int b=46;
RGB_c color={
 unsigned R=255;
 unsigned G=255;
 unsigned B=255;
}
}
载入对象
Stream Library的Load()函数读取类型,名字,和对象值。它创建对象,如果它以前没有创建过,则读取它的属性信息。它通过属性名查找信息并设置它的值。注意流驱动读取数据序列,不是程序或者类结构。载入过程可以容错,可以处理未知的属性,

引用和指针
引用和指针需要特别注意 ,当对象被载入,它们被载入不同的地址.所以Stream Library需要一个地址转换表,用来替换相应的指针和引用的地址。类库的最重要的是如何处理指针。当对象被保存,所有的对象必须被保存一次,即使多个指针引用了它。对象第一次被载入,这时引用通过地址转换表解析引用。这个算法可以通过循环解决。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值