opencascade 第2章 基础类分析 原创
opencascade 第2章 基础类分析
前言
顾名思义,基础类是OCC的基石。它提供了大量的通用服务,如自动动态内存管理(通过句柄对对象进行处理)、集合容器、异常处理、通过向下抛掷和创建插件程序而获得的泛化等。因此,本章将先对OCC的基础类模块进行概述,然后论述其中几个重点部分,如数据类型、集合容器等。
2.1基础类概述
基础类包括根类组件、串类组件、集合容器组件、标准对象的集合容器组件、向量和矩阵类组件、基本几何类型组件、常用数学算法组件、异常类组件、数量类组件和应用程序服务组件。
1、根类组件
根类是基本的数据类型和类,其它所有类都是依此而建立的。它提供以下类型和类:
(1)基本类型,如 Boolean(布尔类型)、Character(字符型)、Integer(整型)或者Real(实型)等。
(2) 动态对象的安全处理,以确保那些不再被引用的对象能被及时删除(详见Standard_Transient类)。
(3)可设置的内存优化管理器。它能改善那些经常使用动态对象的程序性能。
(4) run-time类型信息扩展机制。它使得复杂程序的创建变得更为简易。
(5) 异常管理。
(6)C++各种流的封装。
根类主要在Standard和 MMgt两个包中实现。
2、串类组件
串类用来处理动态大小的ASCI和Unicode字符序列,可以通过句柄处理,因此也可以被共享。串类在TCollection包中实现。
3、集合容器组件
集合容器是处理动态大小的数据集合的类。集合容器是通用的,即每一种集合容器定义了一种结构和一些算法,可持有许多对象——通常这些对象不必从根类继承。这与C++模板相似。如果需要使用一个给定对象类型的集合容器,则必须对这个元素的指定类型进行实例化。一旦这个实例声明被编译,所有基于这个通用集合容器的函数都可以在集合容器对象中实现。
集合容器包含许多通用类,如run-time大小的数组、列表、栈、队列、集(Set)和散列图(hash map)。
集合容器在TCollection和lNCollection包中实现。
4、标准对象的集合容器组件
TColstd包为TCollection包中通用类的一些经常使用的实例化提供对象(来自Standard包)或者串(来自TCollection包)。
5、向量和矩阵类组件
向量和矩阵类提供了有关向量和矩阵的常用数学算法和基本运算(加、乘、转置、求逆等)。
6、基本几何类型组件
基本几何类型提供了基本几何实体和代数实体的实现。这些实现符合STEP( Standard Exchange of Product data model,即产品数据模型的交换标准)。它们提供基本几何Shape 的描述(点、向量、直线、圆与圆锥、平面与基本曲面、通过坐标轴或坐标系使shape在平面上或空间中定位)和 Shape几何变换的定义与应用(平移、旋转、对称、缩放、复合变换、代数计算工具)。
7、常用数学算法组件
常用数学算法为那些经常使用的数学算法提供C++实现。这些算法有:
(1)求解线性代数方程组的算法﹔
(2)求一元或多元函数最小值的算法;
(3)求解非线性方程或非线性方程组的算法;
(4)求矩阵特征值和特征向量的算法。
8、异常类组件
OCC提供了一套异常类。所有异常类都是基于它们的根类—— Failure类的。异常类描述了函数运行期间可能发生的异常情况。发生异常时,程序将不能正常运行。对这种情况的响应称为异常处理。
9、数量类组件
数量类为日期和时间信息提供支持,同时也为表示常用物理量的基本类型(如长度、面积、体积、质量、密度、重量、温度和压力等)提供支持。
10、应用服务组件
应用服务组件包括几种低级服务的实现。借助OCC平台,这些服务可以使
那些允许用户自定义和用户友好的应用程序的创建变得更容易。以下是该组件提供的四种服务:
(1)单位转换工具。它们为各种量和相应物理单位的处理提供统一机制。这种机制能够检查单位的兼容性,以及在两种不同的单位间进行数值转换等(详见UnitsAPI包)。
(2)有关表达的基本解释器。它使得用户脚本工具的建立和表达的通用定义等变得更容易(详见ExprIntrp包)。
(3)用于处理配置资源文件(见Resource包)和用户定制信息文件(见Message包)的工具。有了这些工具,为应用程序提供多语言支持就很容易了。
(4)进程提示和用户中断接口。它们甚至可能为低级算法提供一种综合便利的用户交流方式。
在第二章的基本概念里,已经介绍了OCC数据类型的分类,即依据处理方式的不同,分为值处理类型和句柄处理类型两类。在详细描述这两种类型之前,先描述OCC数据的基本类型。
1、基本类型
所有基本类型都是用CDL预定义的,并且只能通过值处理。依据不同的出自,或者依据各自的存储性能,它们可分为耐存的和非耐存的。耐存的基本类型由Standard_Storable类派生,能应用于持久对象的实现(要么包含于持久对象方法声明的实体中,要么作为持久对象内部结构的一部分)。
下表给出OCC提供的所有基本类型和相应的C++基本类型。
OCC基本类型 | C++基本类型 |
---|---|
Standard_Integer | int |
Standard_Real | double |
Standard_ShortReal | float |
Standard_Boolean | unsigned int |
Standard_False | 0 |
Standard_True | 1 |
Standard_Character | char*(指针类型) |
Standard_Address | void*(指针类型) |
Standard_Extstring | short*(指针类型) |
下面分别论述表中的类型。
(1 ) Standard_Integer(整型)。
它是由32位二进制数表示的基本类型,包括正数、负数和零。Integer类型与C++ int类型一样。因此,可以对Integer类型进行+、-、*、/四种代数运算,也可以对其进行<、<=、==、!=、>=、>六种关系运算。
(2) Standard_Real(实型)。
它表示具有确定精度和确定范围的实数的基本类型。Real类型与C++中double(双精度)类型一样。因此,+、-、、/四种代数运算、-取反运算和<、<=、=、!=、>=、>六种关系运算同样适用于Real类型。*
( 3 ) Standard_ShortReal(短实型)。
它表示具有确定精度和确定范围的实数。ShortReal类型与C++中 float类型一样。因此,+、-、*、/四种代数运算、-取反运算和<、<=、==、!=、>=、>六种关系运算同样适用于 ShortReal类型。
( 4)Standard_Boolean(布尔类型)。
它是描述逻辑值的基本类型。它有两种值: false和l true。Boolean类型与C++中unsigned int类型一样。因此,与、或、异或、非四种代数运算和==、!=两种关系运算同样适用于Boolean类型。
(5 )Standard_Character(字符类型)。
它是用来表示ASCII字符集的一种基本类型。它能被赋予的值有128个,对应128个ASCII字符。Character类型与C++中 char类型一样。因此,<、<=、=-、!=、>=、>六种关系运算同样适
用于Character类型(如: A<B)。
(6)Standard_ExtCharacter(扩展字符类型)。
它是用来表示Unicode字符集的一种基本类型。由它表示的字符得用16位二进制数进行编码。ExtCharacter类型与C++中 short类型一样。因此,<、<=、-=、!=、>=、>六种关系运算同样适用于ExtCharacter类型(如:A<B)。
( 7) Standard_Cstring (c串类型)。
它用来表示文字串。一个文字串就是由双引号括起来的一个ASCII字符序列。CString类型与C++中 char*类型是一样的。
( 8)Standard_Address(地址类型)。
它用来表示一个通用指针。Address类型与C++中 void*类型一样。
(9) Standard_ExtString (扩展串类型)。
它用来表示由Unicode字符序列构成的文字串。ExtString 类型与C++中 short*类型一样。
2、值处理类型
值处理类型可分三大类:
(1) 基本类型;
(2)枚举类型;
(3)由这样一些类(既不是由Standard_Persistent类派生,也不是由Standard_Transient类派生,无论是直接派生还是间接派生)定义的类型。
值处理类型的表现形式比句柄处理类型的表现形式更直接。因此,对值处理类型的操作也会更快。但是值处理类型对象不能单独存于文件中。
下图表示了对一个值处理类型对象的处理过程。
需要注意的是:那些能被数据模式识别(包括基本类型和从Storable类继承过来的类型)的值处理类型可以作为持久对象的部分结构而存储在持久对象内部。这是值处理类型对象能够存进文件的唯一方式。
3、句柄处理类型
句柄处理类型可以分为两大类:
(1) 由Persistent类的派生类定义的类型。这些类型可以被长久地存在文件中。
(2〉由Transient类的派生类定义的类型。
下图
表示了对一个句柄处理类型对象的处理过程。
4、特征总结
为了方便比较值处理类型和句柄处理类型的异同,也为了清楚地认识OCC各种数据类型的处理方式和各自的耐存性,
给出下图
2.2.2句柄
1、句柄的定义
OCC的引用管理采用的是一种句柄机制。这种机制的基本元素是句柄。在OCC中,句柄是通过类实现的。句柄含有多个接口成员,其中一个包含一个引用。一般情况下,仅需要使用它的引用。正因为这样,习惯将句柄比作C++指针。与C++指针一样,几个句柄可以引用同一个对象;一个句柄也可以引用多个对象,但是每次只能引用一个;在句柄访问对象前,句柄必须被声明。
2、句柄处理类的组织
一般情况下,真正需要的是句柄引用的对象而非引用本身。在此,有必要介绍一下句柄处理类的组织。句柄处理类要么是持久的,要么是短暂的。如果由standard_Transient类派生,则是短暂的;如果由 Standard_Persistent类派生,则是持久的。不论短暂还是持久,它们的组织情况是一样的。故,下面一段文字将仅介绍短暂句柄处理类及其相关句柄的组织情况。
Standard_Transient类是OCC中所有句柄处理类的一个根类(另一个根类是Standard_Persistent类)。它提供了一个引用计数器(被其所有后裔类继承)。该计数器被Handle)类(也就是所谓的句柄)使用,用于计算指向对象实例的句柄数。对于每一个继承(直接或间接)自Transient类的类,CDL提取器都创建了相应的Handle()类(句柄)。该Handle()类(句柄)的参数名字和由“Handle_”作前缀修饰的名字一样。OCC专门提供了一个宏预处理函数Handle()。它能够将一个Handle()类(句柄)的参数名字提取出来作为指定短暂类的名字。
这里提三个注意事项:
(1) Transient类和 Persistent类不完全是通过句柄处理的,它们也可以通过值处理;
(2)持久对象不能含有非耐存句柄(那些引用非持久对象的句柄);
(3) 使用句柄的目的是共享对象(对于所有局部操作,建议使用值处理类)。
3、句柄的使用
句柄通过它引用的对象被特征化。在对一个短暂对象进行任何操作之前,必须对句柄进行声明。比如,Point 和 Line是来自Geom包的两个短暂类,声明
得像例3.1这样写。
例:
Handle(Geom_Point) p1, p2;Handle(Geom_Line) aLine;
对一个句柄进行声明,只是创建了一个空句柄,该句柄不指向任何对象。要初始化句柄,要么得创建一个新的对象,要么得将其它句柄值赋予该句柄(假定两种句柄类型是兼容的)。
4、句柄的类型管理
首先介绍句柄的通用管理。
OCC能以通用方式对数据类型进行描述。这样,可以在程序运行时才去严格核对给定对象的类型。这与C++RTTI(运行时类型信息机制)类似。对于每一种由Standard_Transient类派生的类类型, CDL提取器都创建了相应的代码段,用于对Standard_Type类进行实例化。通常Standard _Type类(也称类型描述器)持有类型信息:类型名和其祖先类型列表。
类型的实例(实际上是指向该类型的句柄)由虚函数DynamicType((该虚函数在Standard_Transient类的派生类中)返回。检查给定对象是否具有给定类型或给定类型的后裔类型,需调用另一个虚函数IsKind()。
为给定类类型寻找相关的类型描述器,得用相关的宏STANDARD_TYPEO.其中的宏参数就是给定类的名字。
接着介绍句柄的类型一致原则。
句柄声明中的对象类型是对象的静态类型,它能被编译器识别。句柄能够引用静态类型的子类对象。因此,对象的动态类型(也称对象的实际类型)可以是静态类型的后裔类型。这就是句柄的类型一致原则。
考虑到持久类CartesianPoint是 Point类的一个子类,所以,类型一致原则可以用例3.2表示。
例:
Handle (Geom_Point) p1;
Handle (Geom_CartesianPoint) p2;p2 = new Geom_CartesianPoint;p1 = p2;//可以,类型是兼容的。
例中,编译器将p1看做是指向Point类的句柄,尽管p1 实际指向CartesianPoint类型对象。
最后介绍句柄的直接类型转换。
依据类型一致原则,我们总可以将低层句柄向上赋值给高层句柄。但是,反过来则不行。因此,我们需要一种直接类型转换机制。
如果一个句柄所指对象的实际类型是抛掷者(抛掷句柄的对象)的后裔类型,那么,该句柄可以直接向其子类型转换。这就是句柄的直接类型转换(见例3.3)。
例:
Handle (Geom_Point) p1;
Handle (Geom_CartesianPoint) p2, p3,p2 = new Geom_CartesianPoint;
p1 =p2;//可以,标准的赋值。
p3 = Handle (Geom_CartesianPoint) : : DownCast (p1);
l/可以,p1的实际类型是CartesianPoint句柄,尽管它的静态类型是Point句柄。如果直接转换与句柄所指对象的实际类型不兼容,那么被抛掷的句柄会被清空,并且不会产生任何异常。因此,如果需要一些能在静态类型的子类型中实现的可靠服务,如例3.4编写程序:
例:
void MyFunction (const Handle(A) & a)
Handle (B) b = Handle (B):: Downcast(a);if (! b.IsNullO)
{
//如果B类由A类派生,我们就可以使用“b”"。}
else
//类型不兼容。}
}
向下抛掷尤其被用于处理由不同类型对象组成的集合容器,但是有一个限制条件——这些对象必须继承自同一个根类。例如,有一个由多个短暂对象构成的SequenceOfTransient序列,同时还有两个继承自 Standard_T- ransient类的类,那么例3.5的构造语句是有效的。
例:
Handle(A) a;Handle (B) b;
Handle (Standard_Transient) t;SequenceOfTransient s;
a =new A;
s.Append (a);b = new B;
s.Append (b);t = s.Value (1);
I这里我们不能这样写:a=t,
//这是错误的。
/因此我们向下抛掷:a = Handle (A) :: Downcast (t)if (! a.IsNullO)
{
//类型兼容的话,我们就可以使用“a”。
}
else
//类型不兼容。
}
5、用句柄创建对象
要创建一个由句柄处理的对象,得对句柄进行声明,并用C++ new操作符初始化句柄。这样的声明和初始化必须被构造函数的调用紧跟着,如例3.6所示。
例:
Handle (Geom_CartesianPoint) p;= new Geom_CartesianPoint (0,0, 0);
与C++指针不同,句柄并不支持delete函数。当句柄所指对象不再被使用时,该对象会被自动析构。
6、通过句柄调用对象方法
对于指向持久对象或短暂对象的句柄,可以像使用C++指针那样使用它。通过句柄,可以调用对象方法。
调用对象方法有两种方式:一是使用操作符“>”;二是使用函数调用语句.但是当需要调用方法来测试或者修改句柄状态时,则必须用操作符“."”。
例:说明了怎样获取点的坐标。
Handle (Geom_CartesianPoint) centre;Standard_Real x , y, z;
if(centre.IsNullO){
centre = new PGeom_CartesianPoint (O,0, 0);}
centre->Coord(x, y, z);
说明了怎样获取一个笛卡尔点的类型。
例
Handle(Standard_Transient) p= new Geom_CartesianPoint(O.,0.,0.);if (p->DynamicType() == STANDARD_TYPE(Geom_CartesianPoint) )
cout<<"Type check OK"<<endl;
else
cout<< "Type check FAILED"<<endl;
需要注意的是:如果指向对象方法或对象定义域的句柄是空的,那么将产生NullObject异常。
7、句柄的存储分配
在删除对象前,必须确保对象没有被引用。
为了减少与对象生存管理有关的编程,在每个句柄处理类里都含有一个删除
函数。句柄能使引用计数管理自动进行,并且当对象不再被引用时,自动析构对象。通常,不能对Standard_Transient类的子类实例直接调用delete函数。
当对象有一个新句柄时,引用计数器的值加1。当一个句柄被删除,或被清空,或被重新赋值而指向另一对象时,计数器的值减1。一旦引用计数器的值为o时,对象将被自动析构。
句柄的分配规则可以通过例3.9体现出来。例3.9:
...
Handle (TColstd_HSequenceOfInteger)H1 = new TColstd_HSequenceOfInteger;llH1有一个引用,对应的内存空间是48字节。
{
Handle (TColstd_HSequenceOfInteger)H2;H2= H1;
//H1有两个引用。
if(argc == 3)
{
Handle (TColstd_HSequenceOfInteger)H3;H3= H1;
//H1有三个引用。…
}
//H1有两个引用。
}
//H1有一个引用。
}
//H1没有引用。
// TColStd_HSequenceOfInteger对象被析构。
如果两个或多个对象通过句柄(作为定义域)互相引用,就会出现循环。在这种情况下,对象不会被自动析构。
以图表( graph)为例,它的元素对象得知道它们所属的图表对象,也就是说,元素得有一个对图表对象的引用。如果元素和图表都是通过句柄处理的,并且都以句柄作为自己的定义域,则将出现循环。当最后一个句柄被删除时,图标对象不会被删除。这是因为图表里面还有许多指向该图表的句柄——这些句柄作为图表的数据结构(元素)被存储。
有两种方式可以避免循环的出现:
(1)用C++指针代替每一个引用(比如由元素对图表的引用)。
(2)当图表对象需要被删除时,将整套句柄(如元素中指向图表的句柄)清空。
2.2.3内存管理器
1、使用内存管理器的原因
标准的内存分配有三种方式:静态分配、栈分配和堆分配。静态分配是最简单的内存分配策略。程序中的所有名字在编译时绑定在某个存储位置上;这些绑定不会在运行时改变。块结构语言通过在栈上分配内存,克服了静态分配的一些限制。每次过程调用时,一个活动记录或是帧被压入系统栈,并在返回时弹出。堆分配与栈所遵循的后进先出的规律不同,堆中的数据结构能够以任意次序分配与释放[阁。
建模程序在运行期间,需要构造和析构大量的动态对象。在这种情况下,标准的内存分配函数可能无法胜任工作。因此,OCC采用了特殊的内存管理器(在Standard包中实现)。
2、内存管理器的用法
在C代码中使用OCC内存管理器分配内存,只需用Standard:.Allocate()方法代替malloc()函数,Standard::Free()方法代替free()函数﹐以及Standard::Reallocate()代替reallocO函数。
在C++中,可以将类的newO)操作定义为使用Standard::.Allocate)方法进行内存分配,而将类的delete)操作定义为使用Standard::Allocate()方法进行内存释放。这样就可以使用oCC 内存管理器为所有对象和所有类分配内存。就是用这种方式,CDL提取器为所有用CDL声明的类定义了new()函数和 delete(函数。因此.除了异常类,所有OCC类都使用OCC内存管理器进行存储分配。
因为new()函数和 delete()函数是被继承的,所以对于所有oCC类的派生类(比如,Standard_Transient类的派生类),new)函数和 delete)函数同样适用。
3、内存管理器的配置
OCC 内存管理器可以适用于不同的内存优化技术(不同的内存块采用不同的优化技术,这主要依据内存块的大小而定)。或者,用OCC内存管理器,甚至可以不采用任何优化技术而直接使用c函数malloc(和free(。
内存管理器的配置由下面几个环境变量值定义:
( 1)MMGT_OPT。如果值设为1(默认值),则内存管理器将如下面的描述那样对内存进行优化。如果值设为o,则每个内存块将直接分配(通过mallocO和 free()函数)在C内存堆里。在第二种情况下,所有异常(不包括MMGT_CLEAR异常)都将被忽略。
(2)MMGT_CLEAR。如果值设为1(默认值),则每一个已分配的内存块都将被清零。如果值设为o,则内存块正常返回。
(3)MMGT_CELLSIZE。它定义了大内存池中内存块的最大空间。默认值是200字节。
(4)MMGT_NBPAGES。它定义了页面中由小内存块构成的内存组件(内存池的大小(由操作系统决定)。默认值是1000字节。
(5)MMGT_THRESHOLD。它定义了内存块(能直接在oCC内部被循环使用)的最大空间。默认值是40000字节。
(6)MMGT_MMAP。当值设为1(默认值)时,使用操作系统的映射函数对大内存块进行分配。当值设为o时,大内存块将被malloc(分配在C内存堆里。
(7)MMGT_REENTRANT。当值设为1时,所有对内存优化管理器的调用都将被响应,以保证不同的线程能同时访问内存管理器。在多线程程序中,这个变量值应该设置为1。这里所说的多线程程序是指那些使用oCC内存管理器,并且可能有不止一个调用OCC函数的线程的程序。默认值是0。
在此提一个注意事项:当多线程程序使用oCC以达到最佳内存优化性能时,需要检查两组变量。其中一组是MMGT_OPT=0;另一组则是MMGT_OPT=1和MMGT_REENTRANT=1。
4、内存管理器的实现。
当且仅当MMGT OPT=1时,才用到oCC的特殊的内存优化技术。这些技术有:
(1)小内存块(空间比由MMGT_CELLSIZE设定的值小)不能单独分配,而是分配在大内存池(大小由变量MMGT_NBPAGES决定〉中。每一个内存块分配在当前内存池的空闲部分。若内存池被完全占据,则使用下一个内存池。在当前版本中,在进程结束前,内存池不能返回操作系统。然而,
那些由Standard:Free(释放的内存块被记忆在释放列表中。当需要下一个内存块(与列表中某个块大小相同)时,相应的被释放的那个内存块所占空间可以被新的内存块占用,这也叫内存块的循环使用。
(2)对于中等大小的内存块(比 MMGT_CELLSIZE大,但比 MMGT_THRESHOLD小),它们是被直接分配(通过使用malloc()和 free()在C内存堆里。这些块要是被Standard::Free()方法释放的话,可以像小内存块那样被循环使用。然而,与小内存块不同,那些被记录在释放列表中的可循环使用的中内存块(由持有内存管理器的程序释放)可以被 Standard:Purge)方法返回到c内存堆中。
(3)大内存块(大小比MMGT_THRESHOLD大,包括用来分配小内存块的内存池)的分配取决于MMGT_MMAP的值。如果该值是0,则这些大块被分配在C堆里。否则,它们被操作系统映射函数分配在内存映射文件中。当Standard:Free(被调用时,大块立即被返回到操作系统中去。
5、内存管理器的优缺点。
OCC内存管理器的优点主要体现在小块和中块的循环使用上。当程序需要连续分配和释放大小差不多的内存块时,这个优点能加速程序的执行。实际应用中,这种提升幅度可以高达50%。
相应的,OCC 内存管理器的主要缺点是:程序运行时,被循环使用的内存块不能返回到操作系统中。这可能导致严重的内存消耗,甚至会被操作系统误认为内存泄露。为了减少这种影响,在频繁地对内存进行操作后,OCC系统将调用Standard::Purge)方法。
另外,OCC内存管理器会带来额外的开销,它们有:
(1)舍入后,每个被分配的内存块的大小高达8字节。当MMGT_OPT=0时,舍入值由CRT决定;对32位平台而言,典型值是4字节。
(2)在每个内存块的开端需要额外的4字节以记录该内存块的大小(或者,当内存块被记录在释放列表时,这4字节用来记录下一个内存块的地址)。注意:
只有在MMGT_OPT=1时,才需要这4字节。
需要注意的是:由oCC内存管理器带来的额外开销可能比由C内存堆管理器带来的额外开销大,或者小。因此,整体而言,很难说到底是优化模式的内存消耗大还是标准模式的内存消耗大——这得视情况而定。
通常,编程人员自己也会采用一种优化技术——在内存里面划出一些重要的块。这样就可以将一些连续的数据存于这些块中,使内存页面管理器对这些块的处理变得更容易。
在多线程模式(MMGT_REENTRANT=1)中,oCC内存管理器使用互斥机制以锁定程序对释放列表的访问。因此,当不同的线程经常同时调用内存管理器时,优化模式的性能不如标准模式的性能好。原因是: malloc()函数和free()函数在实现的过程中开辟了几个分配空间——这样就避免了由互斥机制带来的延迟。
2.2.4异常类
1、异常类的定义
异常处理机制实现了正常程序逻辑与错误处理的分离,提高了程序的可阅读性和执行效率。为了转移程序运行的控制流,异常处理的模式通常有无条件转移模式、重试模式、恢复模式和终止模式5]。与C++一样,OCC采用的是终止模式。
为了实现这种异常处理机制,oCC提供了一套异常类。所有异常类都是基于它们的根类——Failure类的。异常类描述了函数运行期间可能发生的异常情况。发生异常时,程序将不能正常运行。对这种情况的响应被称为异常处理。
2、异常类的使用
oCC使用异常的语法与C++使用异常的语法相似。要产生一个确定类型的异常,需用到相应异常类的Raise)方法,如例3.10所示。
例3.10:
DomainError::Raise(" Cannot cope with this condition"“);
这样就产生了一个DomainError类型的异常,同时伴有相应的提示信息“Cannot cope with this condition”。这信息可以是任意的。
该异常可以被某种DomainError类型(DomainError类型派生了好些类型)的句柄器捕获,如例3.11所示。
例3.11:
Try
ocC_CATCH_SIGNALS
ll try块。
catch (DomainError)
l/处理DomainError 异常。}
不能把异常类的使用当作一种编程技巧,例如用异常类代替“goto”。应该把异常类的使用作为方法的一种保护方式(防止被错误使用),即保证方法调用者遇到的问题是方法能处理的。故,在程序正常运行期间,不该产生任何异常。
在使用异常类的时候,需要用一个方法来保护另外一个可能出现异常的方法。这样能通过外层方法来检查内层方法的调用是否有效。例如需要用三个方法(用于检查元素的Value函数、用于检查数组下边界的Lower函数和用于检查数组上边界的Upper函数)使用TCollection_Array1类,那么,Value函数可以如例3.12那样被实现:
例3.12:
Item TCollection_Array1::Value (const Standard_Integer&index) const{
l下面的r1 和 r2是数组的上下边界。if(index <r1 || index > r2)
{
OutOfRange::Raise(“Index out of range in Array1 ::Value””);
return contents[index];
}
在此,OutOfRange::Raise(“Index out of range in Array1:Value”)异常用Lower
函数和Upper函数检查索引是否有效,以保护Value 函数的调用。
一般地,在Value()函数调用前,程序员已确定索引在有效区间内了。这样,上面ValueO函数的实现就不是最优的了,因为检查既费时又冗余。
在软件开发中有这样一种广泛的应用方式,即将一些保护措施置于反汇编构件而非优化构件中。为了支持这种应用,OCC为每一个异常类提供了相应的宏Raise_if():
Raise_if(condition,“Error message”")
这里 ErrorTypeName是异常类型,condition是产生异常的逻辑表达式,而Error message 则是相关的错误信息。
可以在编译的时候,通过No_Exception或者No两种预处理声明之一解除异常的调用,如例3.13所示:
例3.13:
#define No_Exception /解除所有的异常调用使用这构造语句,Value 函数变为:例3.14:
Item TCollection_Array1::Value (const Standard_Integer&index) const
{
OutOfRange_Raise_if(index <r1 || index > r12,
“index out of range in Array1 ::Value”);
return contents[index];
3、异常处理
异常发生时,控制点将转移到调用堆栈中离当前执行点最近的指定类型的句柄器上。该句柄器具有如下特征:
( 1)它的try块刚刚被进入还没有被退出;(2)它的类型与异常类型匹配。
(3)T类型异常句柄器与E类型异常匹配,即T类型和E类型相同,或者T类型是E类型的超类型。
oCC的异常处理机制还可以将系统信号当作异常处理。为此,需要在相关代码的开端嵌入宏occ_CATCH_SIGNALS。建议将这个宏放在try 块中的第一位置。例如,有这样四个异常: NumericError类型异常、Overflow类型异常、Underflow类型异常和ZeroDivide类型异常,其中 NumericError类型是其它三种类型的超类型,那么,异常处理过程如例3.15所示。
例3.15:
void f(1)
{
try
occ_CATCH_SIGNALSll try块
}
catch(Standard_Overflow)
{l第一个句柄器
ll…
catch(Standard_NumericError)
{I/第二个句柄器
ll …
}
}
在这个例子中,第一个句柄器将捕获 Overflow类型异常;第二个句柄器将捕获NumericError类型异常及其派生异常,包括Underflow类型异常和Zerodivide类型异常。异常发生时,系统将从最近的 try块到最远的 try块逐一检查句柄器,直到找到一个在形式上与产生的异常相匹配的为止。
在try块中,如果将基类异常的句柄器置于派生类异常的句柄器之前,则将发生错误。因为那样会导致后者永远不会被调用,如例3.16所示。
例3.16:
void f(1)
{
int i = o;try
occ_CATCH_SIGNALSg(i);
ll/i是可接受的。
/在这放执行语句会导致编译错误!catch(Standard_NumericError)
{
I/依据i值处理异常。}
1/在这放执行语句可能导致不可预料的影响。
}
由异常类形成的树状体系与用户定义的类完全无关。该体系的根类是Failure异常。因此,Failure异常句柄器可以捕获任何OCC异常。建议将Failure异常句柄器设置在主路径中,如例3.17所示。
例3.17:
#include <Standard_ErrorHandler.hxx>#include <Standard_Failure.hxx>
#include <iostream.h>
int main (int argc, char* argv[ ]
Try
{
occ_CATCH_SIGNALS
//主块
return 0;
catch(Standard_Failure)
Handle(Standard_Failure) error = Failure::Caught O;cout <<error <<end1;
return 1;
}
这里的Caught函数是Failure类的一个静态成员,能返回一个含有异常错误信息的异常对象。这种接收对象的方法(通过catch的参数接收异常)代替了通常的C++语句。
尽管标准的C++处理法则和语法在try块和句柄器中同样适用,但在一些平台上,OCC能以一种兼容模式被编译(此时异常支持长转移)。在这种模式中,要求句柄器前后没有执行语句。因此,强烈建议将 try块置于0中。此外,这种模式也要求Standard_ErrorHandler.hxx头文件包含在程序中(置于try块前),否则将不能处理OCC 异常。再有,catch)语句不允许将一个异常对象作为参数来传递。
为了使程序能够像捕获其它异常那样捕获系统信号(如除零),在程序运行时要使用OSD::SetSignal()方法安装相应的信号句柄器。通常,该方法在主函数开端处被调用。
为了能真正的将系统信号转换成OCC异常,oCC_CATCH_SIGNALS宏应该被嵌入到源代码中。典型的,将该宏置于捕获异常的 try块的开端处。
oCC的异常处理机制依据不同的宏预处理NO_Cxx_EXCE-PTIONS和occ_CONVERT_SIGNALS有不同的实现。这些预处理将被OCC或者用户程序的编译程序连贯定义。在 Windows和DEC平台上,这些宏不是以默认值被定义的,并且所有类都支持C++异常,包括从句柄器中抛掷异常。因此,异常的处理与C++异常处理一样。
2.3集合容器和标准对象的集合容器2.3.1―集合容器
1、概述
集合容器组件包含一些具有动态大小的数据集合类,如数组类、列表类和图
( map)类等。集合容器类是通用的,即它们可以持有许多不必从根类继承的对象。当需要使用一个给定对象类型的集合容器时,必须指定集合容器的元素类型.一旦这个声明被编译,所有适用于这个通用集合容器的函数,同样适用于这个集合容器实例。
然而,需要注意两点:
(1)在oCC public构造语句中,被当作参数直接使用的集合容器是在一个oCC组件中实例化的。
( 2 )TColStd包为这些通用集合容器提供许多实例化;实例对象来自Standard包或者串类组件。
集合容器组件提供了一些通用集合容器:
(1)数组。通常用于快速访问项目。但是数组的大小是固定的(大小一旦被声明,将不能更改)。
(2)序列。其大小可变。使用序列可以避免使用大数组和类空数组。但是序列的项目通常比数组的项目长,故只能采用特殊的方法访问序列。另外,许多访问方法不适用于序列。数组和序列通常被用作复杂对象的一种结构。
(3)图(Map)。与序列不同,图的大小可变,而且对图的访问也快。图结构通常可以有多种访问方法。图通常作为复杂算法中的内部数据结构。集(Set)具有同样的用途,但是访问时间要长的多。
(4)列表、队列和栈。它们的结构与序列的结构相似。但是它们的算法却与序列的算法不同。
大部分集合容器遵循语意值,即一个集合容器实例就是实际的集合容器,而不是指向某个集合容器的句柄集合。只有数组和序列才可以通过句柄处理并被共享。
oCC的集合容器有通用集合、通用图和迭代器。它们都在TCollection包中。2、通用集合
通用集合有Array1 .Array2 HArray1 .HArray2 .sequence ,HSequence ,List . Queue , Stack、Set和 HSet。
TCollection_Array1数组与c数组相似,即大小可以由用户在构造时指定,构造后不能更改。与C数组一样,访问Array1成员的时间是一个常量,而与数组的大小无关。Array1数组通常被用作复杂对象的基本结构。Array1数组是一个通用类,它的实际类型取决于它的项目,即数组元素的类型。Array1 的下标区
域由用户定义。因此,要访问Array1项目,必须保证下标在规定的区域内。
TCollection_Array2是二维数组,它的情况与TCollection_Array1的类似。TCollection_HArray1数组与 TCollection_Array1数组相似,不同的是HArray1的对象是指向数组的句柄数组。HArray1数组可以被几个对象共享;可以用TCollection_Array1数组结构作为HArray1的实际结构。HArray1是一个通用类,它的实际类型取决于两个参数:项目(数组的元素类型)和数组(通过HArray1处理的数组的实际类型,具有TCollection_Array1项目类型的一个实例)。
TCollection_HArray2数组与TCollection_HArray1数组类似,只不过是二维的。
TCollection_Sequence是通过整数来索引的序列。序列的用途与一维数组(TCollection_Array1)的一样,都是作为复杂对象的基本结构。但是序列的大小是可变的。使用序列可以避免使用大数组和类空数组。访问序列项目需要专门的方法,否则会比访问数组项目慢。另外也要注意,当需要支持多种访问方法时(图会更适合),序列不是一种有效的结构。序列是一个通用类,它的实际类型取决于项目,即它的元素类型。
TCollection_HSequence 序列TCollection_Sequence序列类似,不同的是HSequence对象是指向序列的句柄序列。HSequence序列可以被几个对象共享;可以用TCollection_Sequence结构作为HSequence 的实际结构。HSequence是一个通用类,它的实际类型取决于两个参数:项目(序列元素的类型)和序列(通过HSequence处理的序列的实际类型,一个具有TCollection_sSequence项目类型的实例)。
TCollection_List是允许项目重复的有序列表。列表可以使用迭代器(ListIterator迭代器)进行线性迭代。可以快速地将一个项目插入列表的任何位置。但是,如果列表很长,那么通过值查找项目将会很慢,因为那样需要逐一查找。故当项目需要通过值来查找时,用序列结构会更好些。列表是一个通用类,