OpenCasCade官方开发文档翻译(2)–occt基础类

介绍

本手册解释了如何使用开放级联技术 ( OCCT ) 基础类。它提供了基础类的基本文档。

基础类提供各种通用服务,例如自动动态内存管理(通过句柄操作对象)、集合、异常处理、通过向下转换的通用性和插件创建。

基础课程包括以下内容:

根类

根类是构建所有其他类的基本数据类型和类。他们提供:

  • 基本类型,例如 Boolean、Character、Integer 或 Real,
  • 安全处理动态创建的对象,确保自动删除未引用的对象(参见Standard_Transient类),
  • 标准和自定义内存分配器,
  • 扩展的运行时类型信息 (RTTI) 机制,便于创建复杂程序,
  • 异常管理,
  • C++ 流的封装。根类主要在标准包中实现。

字符串

字符串是基于 UTF-8 和 UTF-16 编码处理动态大小的字符序列的类。字符串也可以由句柄操作,因此可以共享。字符串在TCollection包中实现。

集合

集合是处理动态大小的数据聚合的类。集合类是通用的并且依赖于 C++ 模板。

集合包括范围广泛的通用类,例如运行时大小的数组、列表、堆栈、队列、集合和哈希映射。集合在TCollectionNCollection包中实现。

标准对象的集合

TColStd包提供了来自TCollection包的通用类的常用实例化,以及来自Standard包的对象或来自TCollection包的字符串。

向量和矩阵

这些类提供了常用的数学算法和涉及向量和矩阵的基本计算(加法、乘法、转置、求逆等)。

原始几何类型

Open CASCADE Technology 原始几何类型是基本几何和代数实体的 STEP 兼容实现。他们提供:

  • 基本几何形状的描述:
    • 积分,
    • 向量,
    • 线条,
    • 圆和圆锥,
    • 平面和基本表面,
  • 通过轴或坐标系在空间或平面中定位这些形状,
  • 这些形状的几何变换的定义和应用:
    • 位移
    • 旋转
    • 对称性
    • 缩放转换
    • 组合变换
  • 代数计算的工具(坐标和矩阵)。

常用数学算法

Open CASCADE Technology 常用数学算法提供了最常用数学算法的 C++ 实现。这些包括:

  • 求解一组线性代数方程的算法,
  • 找到一个或多个自变量的函数的最小值的算法,
  • 找到一个或一组非线性方程的根的算法,
  • 找到方阵的特征值和特征向量的算法。

异常

提供了常用异常类的层次结构,均基于异常根类Standard_Failure 。异常描述了可能在函数执行期间出现的异常情况。随着异常的引发,程序执行的正常过程被放弃。响应这种情况而执行的动作称为异常处理。

日期

这些是支持日期和时间信息的各种类。

应用服务

基础类还包括几个低级服务的实现,这些服务有助于使用 Open CASCADE 技术创建可定制和用户友好的应用程序。这些包括:

  • 单位转换工具,提供处理数量和相关物理单位的统一机制:检查单位兼容性,执行不同单位之间的值转换等(参见包UnitsAPI);
  • 表达式的基本解释器,有助于创建自定义脚本工具、表达式的通用定义等(参见包ExprIntrp);
  • 用于处理配置资源文件(参见包Resource)和可定制消息文件(参见包Message)的工具,便于在应用程序中提供多语言支持;
  • 进度指示和用户中断接口,即使是低级算法也有可能以通用和方便的方式与用户进行通信。

基本

本章涉及基本服务,例如库组织、持久性、数据类型、内存管理、句柄编程、异常处理、通过向下转换的通用性和插件创建。

库组织

本章介绍了一些基本概念,这些概念不仅在基础类中使用,而且贯穿整个 OCCT 库。

模块和工具包

整个 OCCT 库被组织在一组模块中。第一个模块提供最基本的服务并被所有其他模块使用,称为基础类并在本手册中进行了描述。

每个模块主要由一个或多个工具包组成(尽管它也可以包含可执行文件、资源单元等)。从物理上讲,工具包由共享库(例如 .so 或 .dll)表示。该工具包由一个或多个包构建。

一个将许多具有语义链接的类组合在一起。例如,几何包将包含 Point、Line 和 Circle 类。包还可以包含枚举、异常和包方法(函数)。在实践中,类名的前缀是它的包名,例如Geom_Circle。包中描述的数据类型可能包括以下一种或多种数据类型:

  • 枚举
  • 对象类
  • 异常
  • 指向其他对象类的指针 在一个包中,两种数据类型不能具有相同的名称。

图片[1]-OpenCasCade官方开发文档翻译(2)–occt基础类-卡核

包裹的内容

方法要么是函数,要么是过程。函数返回一个对象,而过程仅通过传递参数进行通信。在这两种情况下,当传输的对象是一个由句柄操作的实例时,它的标识符就会被传递。方法分为三类:

  • 对象构造函数创建所描述类的实例。一个类将具有一个或多个具有各种不同参数或没有参数的对象构造函数。
  • 实例方法对拥有它的实例进行操作。
  • 类方法不适用于单个实例,仅适用于类本身。

面向对象软件开发中的基本软件组件是类。类是数据类型的实现。它定义了它的行为(由它的函数提供的服务)和它的表示(类的数据结构——存储它的数据的字段)。

类分为三类:

  • 普通类。
  • 抽象类。抽象类不能被实例化。拥有这样的类的目的是让类的层次结构共享给定的行为,并依赖于后代的实现。这是一种保证基于特定延迟类的所有类共有的某种继承行为基础的方法。
  • 模板类。模板类提供一组功能行为来操作其他数据类型。模板类的实例化需要为其参数指定数据类型。

继承

继承的目的是减少开发工作量。继承机制允许声明一个已经包含现有类特征的新类。然后,这个新类可以快速专门用于手头的任务。这避免了“从头开始”开发每个组件的必要性。例如,已经开发了一个BankAccount类,您可以快速专门化新类:SavingsAccount、LongTermDepositAccount、MoneyMarketAccount、RevolvingCreditAccount等。

其必然结果是,当两个或多个类从父(或祖先)类继承时,所有这些类都至少保证其父(或祖先)的行为。例如,如果父类 BankAccount 包含方法 Print 告诉它自己打印出来,那么它的所有后代类都保证提供相同的服务。

确保使用继承的一种方法是将层次结构顶部的类声明为抽象类。在这样的类中,方法没有实现。这迫使用户创建一个重新定义方法的新类。这是一种保证后代类之间的某种最小行为的方法。

数据类型

面向对象的语言围绕数据类型而不是围绕对这些数据执行的操作来构建系统。在这种情况下,对象是数据类型的一个实例,其定义决定了如何使用它。每种数据类型由一个或多个类实现,这些类构成系统的基本元素。

Open CASCADE Technology 中的数据类型分为两类:

  • 由句柄(或引用)操作的数据类型
  • 按值操作的数据类型

图片[2]-OpenCasCade官方开发文档翻译(2)–occt基础类-卡核

数据类型的操作

数据类型被实现为一个类。该类不仅定义了它的数据表示和实例上可用的方法,而且还建议了如何操作实例。

  • 由值操作的类型的变量包含实例本身。
  • 由句柄操作的类型的变量包含对实例的引用。由值操作的类型的第一个示例是预定义的原始类型Boolean、Character、Integer、Real等。

未附加到对象的句柄操作类型的变量称为null。为了引用一个对象,我们用它的一个构造函数来实例化这个类。例如,在 C++ 中:

Handle(MyClass) anObject = new MyClass();

在 Open CASCADE 技术中,Handles 是特定的类,用于安全地操作通过引用分配在动态内存中的对象,提供引用计数机制和对象未被引用时的自动销毁。

原始类型

原始类型是在语言中预定义的,它们由 value 操作

  • Standard_Boolean用于表示逻辑数据。它可能只有两个值:Standard_TrueStandard_False
  • Standard_Character指定任何 ASCII 字符。
  • Standard_ExtCharacter是扩展字符。
  • Standard_Integer是一个整数。
  • Standard_Real表示一个实数(即一个整数和一个小数部分,其中任何一个都可以为空)。
  • Standard_ShortReal是一个实数,具有较小的值和内存大小选择。
  • Standard_CString用于文字常量。
  • Standard_ExtString是一个扩展字符串。
  • Standard_Address表示大小未定的字节地址。

每种类型提供的服务都在标准包中进行了描述。下表显示了 C++ 基本类型和 OCCT 基本类型之间存在的等价性。

表 1:C++ 类型和 OCCT 原始类型之间的等价性

C++ Types OCCT Types
int Standard_Integer
double Standard_Real
float Standard_ShortReal
bool Standard_Boolean
char Standard_Character
char16_t Standard_Utf16Char
char* Standard_CString
void* Standard_Address
char16_t* Standard_ExtString
  • 带星号的类型是指针。

注意上面列出的类:

  • Standard_Integer:表示产生负、正或空值的 32 位整数的基本类型。Integer被实现为C++ int基本类型的typedef 。因此,在其上定义了代数运算 +、-、*、/ 以及排序和等价关系 <、<=、==、!=、>=、>。
  • Standard_Real:表示具有有限精度和有限大小的实数的基本类型。Real被实现为C++ double(双精度)基本类型的typedef 。因此,代数运算 +、-、*、/、一元- 以及排序和等价关系 <、<=、==、!=、>=、> 是在实数上定义的。
  • Standard_ShortReal:表示具有有限精度和有限大小的实数的基本类型。ShortReal被实现为C++浮点(单精度)基本类型的typedef 。因此,代数运算 +、-、*、/、一元- 以及排序和等价关系 <、<=、==、!=、>=、> 是在实数上定义的。
  • Standard_Boolean:表示逻辑表达式的基本类型。它有两个值:falsetrueBoolean被实现为C++ bool基本类型的typedef 。因此,代数运算and, or, xor and not以及等价关系 == 和 != 在布尔值上定义。
  • Standard_Character:表示 UTF-8 字符集的基本类型。Character被实现为C++ char基本类型的typedef 。因此,排序和等价关系 <、<=、==、!=、>=、> 是使用 ASCII 图表的顺序在字符上定义的(例如:AB)。
  • Standard_ExtCharacter:表示 UTF-16 字符集的基本类型。它是 16 位字符类型。ExtCharacter被实现为C++ char16_t基本类型的typedef 。因此,排序和等价关系 <、<=、==、!=、>=、> 使用 UNICODE 图表(例如:AB)的顺序在扩展字符上定义。
  • Standard_CString:表示字符串文字的基本类型。字符串文字是用双引号括起来的 UTF-8(8 位)代码点序列。CString被实现为C++ char基本类型的typedef 。
  • Standard_Address:表示通用指针的基本类型。Address实现为C++ void基本类型的typedef 。
  • Standard_ExtString:将字符串文字表示为 Unicode(16 位)字符序列的基本类型。ExtString被实现为C++ char16_t基本类型的typedef 。

按值操作的类型

按值操作的类型分为三类:

  • 原始类型
  • 枚举类型
  • 由不直接继承自Standard_Transient的类定义的类型,无论是否直接。通过值操作的类型比通过句柄操作的类型表现得更直接,因此可以预期更快地执行操作,但它们不能独立存储在文件中。

图片[3]-OpenCasCade官方开发文档翻译(2)–occt基础类-卡核

按值操作数据类型

通过引用(句柄)操作的类型

这些是由继承自Standard_Transient类的类定义的类型。

图片[4]-OpenCasCade官方开发文档翻译(2)–occt基础类-卡核

通过引用操作数据类型

什么时候需要使用句柄?

当您设计一个对象时,可能很难选择如何操作该对象:按值或按句柄。以下想法可以帮助您下​​定决心:

  • 如果您的对象在应用程序中的生命周期可能很长,并且您希望对其进行多次引用,那么最好使用句柄来操作该对象。对象的内存将在堆上分配。指向该内存的句柄是一个轻量级对象,可以在参数中快速传递。这避免了复制大对象的惩罚。
  • 如果您的对象将具有有限的生命周期,例如,在单个算法中使用,则最好按值操作此对象,而不考虑其大小,因为此对象是在堆栈上分配的,并且分配和取消分配内存的分配速度非常快,这避免了在堆上分配时对newdelete的隐式调用。
  • 最后,如果一个对象在应用程序的整个生命周期中只创建一次,但在整个生命周期中都存在,那么最好的选择可能是由句柄操作的类或声明为全局变量的值。

使用句柄编程

句柄定义

句柄是智能指针的 OCCT 实现。多个句柄可以引用同一个对象。此外,一个句柄可以引用多个对象,但一次只能引用一个。要访问它所引用的对象,必须像使用 C++ 指针一样取消引用句柄。

类组织

Class Standard_Transient是 OCCT 类的大层次结构的根,据说这些类可由句柄操作。它提供了一个引用计数器字段,由其所有后代类继承,关联的Handle()类使用该字段来跟踪指向该对象实例的多个句柄。

从Transient派生(直接或间接)的类的对象通常使用 operator new在动态内存中分配,并由句柄操作。句柄被定义为模板类opencascade::handle<>。Open CASCADE Technology 提供了预处理器宏Handle(),它在 OCCT 代码中一直用于命名句柄:

Handle( Geom_Line ) 一条线;// “Handle(Geom_Line)” 扩展为 “opencascade::handle<Geom_Line>”

此外,对于大多数 OCCT 类,为句柄定义了额外的typedef ,作为前缀为Handle_的类的名称。例如,上面的例子也可以编码为:

Handle_Geom_Line aLine; // “Handle_Geom_Line” 是 typedef 到 “opencascade::handle<Geom_Line>”

使用句柄

句柄的特征在于它引用的对象。

在对瞬态对象执行任何操作之前,您必须声明句柄。例如,如果 Point 和 Line 是 Geom 包中的两个瞬态类,您可以编写:

Handle( Geom_Point ) p1, p2;

声明句柄会创建一个不引用任何对象的空句柄。可以通过其方法IsNull()检查句柄是否为空。要使句柄无效,请使用方法Nullify()

要初始化一个句柄,要么应该创建一个新对象,要么可以为其分配另一个句柄的值,条件是它们的类型兼容。

请注意,句柄只能用于对象共享。对于所有本地操作,建议使用由值操作的类。

类型管理

开放级联技术提供了一种以通用方式描述数据类型层次结构的方法,可以在运行时检查给定对象的确切类型(类似于 C++ RTTI)。

要启用此功能,类声明应包含 OCCT RTTI 的声明。标头Standard_Type.hxx提供了两种预处理器宏变体来促进这一点:

这些宏定义了返回类型描述符的方法DynamicType() – 描述该类的Standard_Type类的单例实例的句柄。类型描述符存储类的名称和其父类的描述符。

请注意,虽然内联版本更易于使用,但对于广泛使用的类,由于内联方法的多个实例化,此方法可能会导致依赖库的二进制代码膨胀。

要获取给定类类型的类型描述符,请使用宏STANDARD_TYPE()并将类的名称作为参数。

使用示例:

if (aCurve->IsKind( STANDARD_TYPE ( Geom_Line ))) // 等价于 “if (dynamic_cast<Geom_Line>(aCurve.get()) != 0)”
{
}

类型一致性

句柄声明中使用的类型是对象的静态类型,即编译器看到的类型。句柄可以引用从其静态类型的子类实例化的对象。因此,对象的动态类型(也称为对象的实际类型)可以是出现在处理它的句柄声明中的类型的后代。

考虑Geom_CartesianPoint 类,它是 Geom_Point子类;类型一致性规则可以说明如下:

Handle( Geom_Point ) aPnt1;
aPnt2 = new Geom_CartesianPoint ();
aPnt1 = aPnt2; // OK,类型兼容

编译器将aPnt1视为Geom_Point的句柄,尽管aPnt1引用的实际对象属于Geom_CartesianPoint类型。

显式类型转换

根据类型一致性的规则,总是可以通过句柄的连续分配来提升类层次结构。另一方面,分配并没有授权你沿着层次结构走下去。因此,需要对句柄进行显式类型转换。

如果引用对象的实际类型是用于转换句柄的对象的后代,则可以将句柄显式转换为其子类型之一。如果不是这种情况,则句柄无效(显式类型转换有时称为“安全强制转换”)。考虑下面的例子。

Handle( Geom_Point ) aPnt1;
Handle( Geom_CartesianPoint ) aPnt2, aPnt3;
aPnt2 = new Geom_CartesianPoint ();
aPnt1 = aPnt2;// 好的,标准赋值
aPnt3 =Handle( Geom_CartesianPoint )::DownCast (aPnt1);
// OK,aPnt1的实际类型是Geom_CartesianPoint,虽然句柄的静态类型是Geom_Point

如果转换与引用对象的实际类型不兼容,则“强制转换”的句柄变为 null(并且不会引发异常)。因此,如果您需要在句柄所见类型(静态类型)的子类中定义可靠服务,请编写如下:

void MyFunction ( const Handle (A) & a )
{
Handle(B) b = Handle(B)::DownCast( a );
if (! b .IsNull()) {
// 如果类 B 继承自 A,我们可以使用“b”
}
否则{
// 类型不兼容
}
}

向下转型特别适用于不同类型对象的集合;但是,这些对象应该继承自同一个根类。

例如,对于瞬态对象序列TColStd_SequenceOfTransient和两个类 A 和 B 都继承自Standard_Transient,您将获得以下语法:

Handle(A) a;
Handle(B) b;
a = new A();
aSeq.Append (a);
b = new B();
aSeq.Append (b);
t = aSeq.Value (1);
// here, you cannot write:
// a = t; // ERROR !
// so you downcast:
a = Handle (A)::Downcast (t)
if (!a.IsNull())
{
// types are compatible, you can use a
}
else
{
// the types are incompatible
}

使用句柄创建对象

要创建由句柄操作的对象,请声明句柄并使用标准 C++ new运算符对其进行初始化,然后立即调用构造函数。构造函数可以是在实例化对象的类的源中指定的任何构造函数。

Handle( Geom_CartesianPoint ) aPnt;
aPnt = new Geom_CartesianPoint (0, 0, 0);

与指针不同,delete运算符不适用于句柄。当不再使用时,引用的对象会自动销毁。

调用方法

一旦有了对象的句柄,就可以像使用 C++ 中的指针一样使用它。要调用作用于被引用对象的方法,您可以通过标准箭头运算符转换此方法,或者在可用时通过函数调用语法来转换此方法。

为了测试或修改句柄的状态,该方法由运算符翻译。下面的示例说明了如何访问(可选初始化)点对象的坐标:

if (aCentre.IsNull())
{
aCentre = new PGeom_CartesianPoint (0, 0, 0);
}
aCentre->Coord (x, y, z);

下面的例子说明了如何访问笛卡尔点的类型对象:

Handle( Standard_Transient ) aPnt = new Geom_CartesianPoint (0., 0., 0.);
if (aPnt->DynamicType() == STANDARD_TYPE ( Geom_CartesianPoint ))
{
std::cout << “类型检查正常\n” ;
}
别的
{
std::cout << “类型检查失败\n” ;
}

如果通过Null句柄访问对象的字段或方法,则会引发Standard_NullObject异常。

调用类方法

类方法的调用类似于静态 C++ 函数,即通过它所属的类的名称来调用它,后跟“::”运算符和方法的名称。

例如,我们可以找到贝塞尔曲线的最大度数:

处理解除分配

在您删除一个对象之前,您必须确保它不再被引用。为了减少与对象寿命管理相关的编程负担,Open CASCADE 技术中的删除功能由句柄操作的类的引用计数器保护。当不再引用对象时,句柄会自动删除它。通常,您永远不会在Standard_Transient的子类实例上显式调用 delete 运算符。

当创建同一对象的新句柄时,引用计数器会递增。当句柄被销毁、无效或重新分配给另一个对象时,该计数器会递减。当引用计数器变为 0 时,该对象被句柄自动删除。

分配原理见下例。

{
Handle(TColStd_HSequenceOfInteger) H1 = new TColStd_HSequenceOfInteger();
// H1有一个引用,对应48字节内存
{
Handle(TColStd_HSequenceOfInteger)H2;
H2 = H1;// H1有两个引用
if(argc == 3)
{
Handle(TColStd_HSequenceOfInteger) H3​​;
H3 = H1;
// 这里,H1有3个引用
}
// 这里,H1有两个引用
}
// 这里,H1 有 1 个引用
}
// 这里,H1 没有引用,被引用的 TColStd_HSequenceOfInteger 对象被删除。

通过定义以下内容,您可以轻松地将句柄对象的引用转换为void* :

无效* 指针;
Handle(Some_Class)一个句柄;
// 这里只会复制一个指针
aPointer = &aHandle;
// 这里将复制 Handle 对象
aHandle = *(Handle(Some_Class)*)aPointer;

循环

如果两个或多个对象通过句柄(存储为字段)相互引用,则会出现循环。在这种情况下,自动销毁将不起作用。

例如,考虑一个图,其对象(原语)必须知道它们所属的图对象,即原语必须具有对完整图对象的引用。如果图元和图形都由句柄操作,并且它们通过将句柄作为字段来相互引用,则出现循环。

图对象的最后一个句柄在应用程序中被破坏时不会被删除,因为它的句柄存储在它自己的数据结构(原语)中。

有两种方法可以避免这种情况:

  • 将 C++ 指针用于一种引用,例如从原语到图形
  • 当需要销毁图形对象时,将一组句柄(例如图元中的句柄)无效

内存管理

在工作会话中,几何建模应用程序创建和删除分配在动态内存(堆)中的大量 C++ 对象。在这种情况下,用于分配和释放内存的标准函数的性能可能不够。出于这个原因,Open CASCADE 技术采用了标准包中实现的专用内存管理器。

内存管理器基于以下原则:

  • 小内存阵列被分组到集群中,然后被回收(集群永远不会释放到系统中),
  • 大数组通过系统的标准函数分配和释放(数组不再使用时释放到系统)。

作为一般规则,建议通过重要块分配内存。通过这种方式,用户可以使用连续数据块,并且有助于内存页面管理器的处理。

内存管理器的使用

要使用 Open CASCADE Technology 内存管理器在 C 代码中分配内存,只需使用方法Standard::Allocate()代替malloc()和方法Standard::Free()代替free()。此外,提供了方法Standard::Reallocate()来替换 C 函数realloc()

在 C++ 中,可以定义类的运算符new()delete()以便使用Standard::Allocate()分配内存并使用Standard::Free()释放它。在这种情况下,该类的所有对象和所有继承的类都将使用 OCCT 内存管理器进行分配。

头文件Standard_DefineAlloc.hxx提供的预处理宏DEFINE_STANDARD_ALLOC以这种方式定义了new()delete()。它用于所有 OCCT 类(除了少数例外),因此使用 OCCT 内存管理器进行分配。由于运算符new()delete()是继承的,因此对于从 OCCT 类派生的任何类也是如此,例如,对于从Standard_Transient派生的所有类。

请注意,可以为继承Standard_Transient的类重新定义new()delete()函数(尽管不推荐,除非确实不可避免)。如果这样做了,还应重新定义方法Delete()以将 operator delete应用于此指针。这将确保调用适当的delete()函数,即使对象是由基类的句柄操作的。

如何配置内存管理器

OCCT 内存管理器可以配置为将不同的优化技术应用于不同的内存块(取决于它们的大小),甚至可以避免任何优化并直接使用 C 函数malloc()free()。配置由以下环境变量的数值定义:

  • MMGT_OPT
    • 如果设置为 0(默认),则每个内存块都直接在 C 内存堆中分配(通过malloc()free()函数)。在这种情况下,除了MMGT_CLEAR之外的所有其他选项都将被忽略;
    • 如果设置为 1,则内存管理器执行如下所述的优化;
    • 如果设置为 2,则使用英特尔® TBB 优化内存管理器。
  • MMGT_CLEAR:如果设置为 1(默认),则每个分配的内存块都被零清除;如果设置为 0,则按原样返回内存块。
  • MMGT_CELLSIZE:定义在大型内存池中分配的块的最大大小。默认值为 200。
  • MMGT_NBPAGES:定义为页面中的小块分配的内存块的大小(取决于操作系统)。默认值为 1000。
  • MMGT_THRESHOLD:定义在内部回收而不是返回到堆的块的最大大小。默认值为 40000。
  • MMGT_MMAP:设置为 1(默认)时,使用操作系统的内存映射函数分配大内存块;如果设置为 0,它们将由malloc()在 C 堆中分配。

优化技术

MMGT_OPT设置为 1 时,使用以下优化技术:

  • 大小小于MMGT_CELLSIZE的小块不会单独分配。相反,分配了大量内存池(每个池的大小为MMGT_NBPAGES页)。每个新的内存块都安排在当前池的空闲位置。当当前内存池被完全占用时,分配下一个内存池,以此类推。

在当前版本中,内存池永远不会返回给系统(直到进程完成)。但是,通过Standard::Free()方法释放的内存块会在空闲列表中被记住,然后在分配相同大小的下一个块时重用(回收)。

  • 大小大于MMGT_CELLSIZE但小于MMGT_THRESHOLD的中型块直接在 C 堆中分配(使用malloc()free())。当这些块通过Standard::Free()方法释放时,它们就像小块一样被回收。

然而,与小块不同的是,空闲列表中包含的回收的中块(即由程序释放但由内存管理器持有)可以通过方法Standard::Purge()返回到堆中。

  • 大小大于MMGT_THRESHOLD的大块,包括用于小块的内存池,根据MMGT_MMAP的值进行分配:如果为 0,则在 C 堆中分配这些块;否则,它们是使用管理内存映射文件的操作系统特定功能分配的。调用Standard::Free()时,大块立即返回系统。

优点和缺点

OCCT 内存管理器的主要优势在于它对中小型块的循环利用,当应用程序不断分配和释放多个类似大小的内存块时,它可以更快地运行。在实际情况下,应用程序性能的实际增益可能高达 50%。

相关的缺点是在程序执行期间回收的内存不会返回给操作系统。这可能会导致相当大的内存消耗,甚至被误解为内存泄漏。为了尽量减少这种影响,有必要在内存密集型操作完成后调用方法Standard::Purge 。

OCCT 内存管理器引起的开销为:

  • 每个分配的内存块的大小四舍五入为 8 字节(当MMGT_OPT为 0(默认)时,四舍五入由 CRT 定义;32 位平台的典型值为 4 字节)
  • 仅当MMGT_OPT为 1时,在每个内存块的开头分配额外的 4 个字节(或 64 位平台上的 8 个字节)以保存其大小(或在空闲列表中回收时下一个空闲内存块的地址) 。

请注意,这些开销可能大于或小于 C 堆内存管理器引起的开销,因此在优化模式或标准模式下,总内存消耗可能会更大,具体取决于具体情况。

作为一般规则,建议通过重要块分配内存。通过这种方式,您可以处理连续数据块,并为内存页管理器的处理提供便利。

OCCT 内存管理器使用互斥锁来锁定对空闲列表的访问,因此在不同线程经常同时调用内存管理器的情况下,它的性能可能低于非优化模式。原因是malloc()free()的现代实现采用了多个分配区域,因此避免了等待互斥锁释放的延迟,这在这种情况下是可能的。

异常

介绍

任何对象的行为都由其类声明中定义的方法实现。这些方法的定义不仅包括它们的签名(它们的编程接口),还包括它们的有效性域。

此域由异常表示。在各种错误条件下引发异常以保护软件质量。

异常处理提供了一种将控制从正在执行的程序中的给定点转移到与先前执行的另一个点相关联的异常处理程序的方法。

一个方法可能会引发一个异常,该异常会中断其正常执行并将控制权转移给捕获该异常的处理程序。

提供了常用异常类的层次结构。根类是Standard包中的Standard_Failure。因此,每个异常都直接从Standard_Failure继承或从另一个异常继承。异常类列出了所有可以由任何 OCCT 函数引发的异常。

Open CASCADE 技术还支持将系统信号(例如访问冲突或被零除)转换为异常,以便可以使用相同的统一方法安全地处理此类情况。

但是,为了在各种平台上支持此功能,使用了一些特殊的方法和变通方法。虽然实现细节是隐藏的,OCCT 异常的处理方式与 C++ 基本相同,但应考虑到这种方法的一些特殊性,并且必须遵守一些规则。

以下段落描述了在使用 Open CASCADE 技术时使用异常的推荐方法。

引发异常

“类似 C++”的语法

下面的例子:

throw Standard_DomainError ( “无法应对这种情况” );

引发Standard_DomainError类型的异常以及相关消息“无法应对这种情况”,该消息是可选的。此异常可能被Standard_DomainError类型的处理程序捕获,如下所示:

try
{
// 尝试块
}
catch( const Standard_DomainError& )
{
// 在此处处理 Standard_DomainError 异常
}

经常使用

异常不应用作编程技术,例如替换“goto”语句,而应用作保护方法不被滥用的一种方式。调用者必须确保它的条件是该方法可以处理它。

因此,

  • 在应用程序的正常执行期间不应引发异常。
  • 可能引发异常的方法应受到其他方法的保护,允许调用者检查调用的有效性。

例如,如果您考虑使用TCollection_Array1类:

  • 提取元素的值函数;
  • Lower函数提取数组的下界;
  • Upper函数提取数组的上限。

那么,Value函数可以实现如下:

Item TCollection_Array1::Value ( Standard_Integer theIndex) const
{
// 其中 myR1 和 myR2 是数组的下界和上界
if (theIndex < myR1 || theIndex > myR2)
{
throw Standard_OutOfRange ( “TCollection_Array1::Value 中的索引超出范围” );
}
返回我的内容[theIndex];
}

这里首先使用 Lower 和 Upper 函数验证索引的有效性,以保护调用。通常,调用者在调用Value()之前确保索引在有效范围内。在这种情况下, Value的上述实现并不是最优的,因为在Value中完成的测试既耗时又多余。

在程序的调试版本中包含这种保护并在发布(优化)版本中排除这种保护是一种广泛使用的做法。为了支持这种做法,为每个 OCCT 异常类提供了宏Raise_if() :

<ErrorTypeName>_Raise_if(condition, “Error message” );

其中ErrorTypeName是异常类型,condition是导致引发异常的逻辑表达式,Error message是相关消息。

通过在编译时定义预处理器符号No_ExceptionNo_<ErrorTypeName>之一,可以删除整个调用:

#define No_Exception // 删除所有加注

使用此语法,Value函数变为:

Item TCollection_Array1::Value ( Standard_Integer theIndex) const
{
Standard_OutOfRange_Raise_if (theIndex < myR1 || theIndex > myR2, “TCollection_Array1::Value 中的索引超出范围” );
return myContents[theIndex];
}

处理异常

当引发异常时,控制将转移到调用堆栈中给定类型的最近处理程序,即:

  • 其 try 块最近进入但尚未退出的处理程序,
  • 类型与 raise 表达式匹配的处理程序。

T 异常类型的处理程序匹配具有 E 异常类型的 raise 表达式,如果:

  • T 和 E 属于同一类型,或
  • T 是 E 的超类型。

为了将系统信号作为异常处理,请确保在相关代码开头的某处插入宏OCC_CATCH_SIGNALS 。它的推荐位置是打开try {}块的大括号后的第一个语句。

例如,考虑Standard_NumericError、Standard_Overflow、Standard_UnderflowStandard_DivideByZero类型的异常,其中Standard_NumericError是其他三个类型的父类型。

void f(1)
{
try
{
// try block
}
catch (const Standard_Overflow& ) // first handler
{
// …
}
catch (const Standard_NumericError& ) // second handler
{
// …
}
}

在这里,第一个处理程序将捕获Standard_Overflow类型的异常,第二个处理程序将捕获Standard_NumericError类型的异常以及从它派生的所有异常,包括Standard_UnderflowStandard_DivideByZero

处理程序按照出现的顺序进行检查,从离 try 块最近的到离它最远的,直到一个匹配 raise 表达式。对于 try 块,将基异常类型的处理程序放在其派生类型的处理程序之前是错误的,因为这将确保永远不会调用派生异常的处理程序。

void f(1)
{
int i = 0;
{
try
{
g(i);// i is accessible
}
// statement here will produce compile-time errors !
catch (const Standard_NumericError& )
{
// fix up with possible reuse of i
}
// statement here may produce unexpected side effect
}
. . .
}

异常形成一个与其他用户定义的类完全分离的层次结构树。Standard_Failure类型的一个异常是整个异常层次结构的根。因此,使用具有Standard_Failure类型的处理程序会捕获任何 OCCT 异常。建议在主程序中设置这样的处理程序。

程序的主要例程如下所示:

#include <iostream>
int main ( int argc, char * argv[])
{
try
{
// 主块
返回0;
}
catch ( const Standard_Failure & theFailure)
{
std::cerr << “错误 “ + theFailure. DynamicType ()->Name() << ” [“ << theFailure. GetMessageString () << “]\n” ;
}
返回1;
}

尽管标准 C++ 范围规则和语法适用于 try 块和处理程序,但请注意,在某些平台上,当通过长跳转模拟异常时,Open CASCADE 技术可能会以兼容模式编译(见下文)。在这种模式下,要求在任何处理程序之前或之后都没有语句。因此,强烈建议始终在附加的 {} 大括号中包含一个 try 块。此模式还要求在 try 块之前将头文件Standard_ErrorHandler.hxx包含在程序中,否则可能无法处理 Open CASCADE 技术异常。

捕捉信号

为了使应用程序能够以与其他异常相同的方式捕获系统信号(访问冲突、被零除等),应通过方法OSD::SetSignal()在运行时安装适当的信号处理程序.

通常在 main() 函数的开头调用此方法。它安装了一个处理程序,将系统信号转换为 OCCT 异常。

为了真正将信号转换为异常,需要在源代码中插入宏OCC_CATCH_SIGNALS 。放置此宏的典型位置是捕获此类异常的try{}块的开头。

在各种平台上实现

Open CASCADE 技术中的异常处理机制根据预处理宏OCC_CONVERT_SIGNALS以不同的方式实现,这应由 Open CASCADE 技术和用户应用程序的编译过程一致定义:

  1. 在 Windows 上,默认情况下未定义这些宏,并且在所有情况下都使用正常的 C++ 异常,包括从信号处理程序中抛出。因此,行为与 C++ 中的预期一致。
  2. 在 Linux 上,宏OCC_CONVERT_SIGNALS是默认定义的。C++ 异常机制用于捕获异常并将它们从普通代码中抛出。由于不可能从系统信号处理函数抛出 C++ 异常,因此该函数会长跳转到最近的(在执行堆栈中)宏OCC_CATCH_SIGNALS调用,并且只有在那里实际抛出 C++ 异常。宏OCC_CATCH_SIGNALS在文件Standard_ErrorHandler.hxx中定义。因此,包含此文件对于成功编译包含此宏的代码是必要的。

    此模式与仅针对信号的标准 C++ 异常处理不同:

    • OCC_CATCH_SIGNALS是必要的(除了上述对OSD::SetSignal()的调用)将信号转换为异常;
    • 在该宏之后的代码中创建的自动 C++ 对象的析构函数,直到出现信号的地方将不会在信号的情况下被调用,因为不会通过长跳转执行 C++ 堆栈展开。

一般来说,为了编写与平台无关的代码,建议在 try {} 块或可能发生信号的其他代码中插入宏OCC_CATCH_SIGNALS 。

插件管理

通过插件分发

插件是可以动态加载到客户端应用程序中的组件,不需要直接链接到它。插件不绑定其客户端,即插件只知道它的连接机制是如何定义的,以及如何调用相应的服务。

插件可用于:

  • 实现驱动程序的机制,即根据当前事务动态更改驱动程序实现(例如,检索存储在另一个版本的应用程序中的文档),
  • 将处理资源限制在所需的最低限度(例如,只要用户不需要它们,它就不会在运行时加载任何应用程序服务),
  • 促进模块化开发(应用程序可以提供基本功能,而一些高级功能将在可用时作为插件添加)。

插件是在全局通用标识符 (GUID) 的帮助下识别的。GUID 包含小写字符并且不能以空格结尾。

加载后,直接调用插件提供的服务(客户端使用与插件相同的语言实现)。

C++ 插件实现

C++ 插件将服务实现为具有在抽象类中定义的功能的对象(此抽象类及其具有 GUID 的父类是有关在客户端应用程序中实现的插件的唯一信息)。该插件由一个可共享库组成,其中包括一个名为 Factory 的方法,该方法创建 C++ 对象(客户端无法实例化该对象,因为插件实现不可见)。基础类在包插件中提供了一个名为Load()的方法,它使客户端能够通过库访问所需的服务。

该方法从环境变量CSF_PluginDefaults找到的资源文件Plugin中读取有关可用插件及其位置的信息:

$CSF_PluginDefaults/插件

Load方法通过其GUID 在资源文件或注册表中查找库名称,例如,在 UNIX 上:

!METADATADRIVER,其值必须是 OS 或 DM。
!固件
a148e300-5740-11d1-a904-080036aaa103.位置:libFWOSPlugin.so

然后Load方法根据宿主机操作系统的规则加载库(例如,在 Unix 上使用LD_LIBRARY_PATH和在 Windows上使用PATH等环境变量)。之后,它调用PLUGINFACTORY方法返回支持所需服务的对象。然后客户端可以调用此对象支持的功能。

C++ 客户端插件实现

要调用插件提供的服务之一,您可以使用所请求服务的Standard_GUID调用Plugin::Load()全局函数,如下所示:

Handle(FADriver_PartStorer)::DownCast(PlugIn::Load (yourStandardGUID));

我们以FAFactory.hxxFAFactory.cxx为例:

class FAFactory
{
public:
Standard_EXPORT static Handle(Standard_Transient) Factory (const Standard_GUID& theGUID);
};
#include <FAFactory.hxx>
#include <FADriver_PartRetriever.hxx>
#include <FADriver_PartStorer.hxx>
#include <FirstAppSchema.hxx>
#include <FACDM_Application.hxx>
#include <Plugin_Macro.hxx>
static Standard_GUID StorageDriver (“45b3c690-22f3-11d2-b09e-0000f8791463”);
static Standard_GUID RetrievalDriver(“45b3c69c-22f3-11d2-b09e-0000f8791463”);
static Standard_GUID Schema (“45b3c6a2-22f3-11d2-b09e-0000f8791463”);
//======================================================
// function : Factory
// purpose :
//======================================================
Handle(Standard_Transient) FAFactory::Factory (const Standard_GUID& theGUID)
{
if (theGUID == StorageDriver)
{
std::cout << “FAFactory : Create store driver\n”;
static Handle(FADriver_PartStorer) sd = new FADriver_PartStorer();
return sd;
}
if (theGUID == RetrievalDriver)
{
std::cout << “FAFactory : Create retrieve driver\n”;
static Handle(FADriver_PartRetriever) rd = new FADriver_PartRetriever();
return rd;
}
if (theGUID == Schema)
{
std::cout << “FAFactory : Create schema\n”;
static Handle(FirstAppSchema) s = new FirstAppSchema();
return s;
}
throw Standard_Failure (“FAFactory: unknown GUID”);
}
// export plugin function “PLUGINFACTORY”
PLUGIN(FAFactory)

应用程序还可以通过链接到库并直接调用FAFactory::Factory()来实例化工厂。

集合、字符串、数量和单位转换

集合

概述

Collections组件包含处理动态大小的数据聚合的类。它们包括范围广泛的集合,例如数组、列表和映射。

一些 OCCT 集合在现代 STL(标准模板集合)中有亲密的朋友,但定义了一些不同的属性或行为。OCCT 为用户提供了更广泛的选择,但由用户决定哪个特定的 OCCT 或 STL 集合最适合特定算法(包括性能和使用便利性)。由于历史原因,OCCT 本身高度依赖自己的集合——OCCT 实现的许多功能在早期版本的 STL 中是不可用的。

集合类是通用的(C++ 模板),也就是说,它们定义了一个结构和算法,允许保存不一定从唯一的根类继承的各种对象。

注意:

  • 每个在 OCCT 公共语法中直接用作参数的集合都在 OCCT 组件中实例化。
  • TColStd包(标准对象集合组件提供了这些通用集合的大量实例,其中包含来自标准包或字符串组件的对象。

Collections组件提供了广泛的通用集合:

  • 数组通常用于快速访问项目,但是数组是固定大小的聚合。
  • 序列是可变大小的结构,它们避免使用大型和准空数组。一个序列项比一个数组项访问的时间更长:只有在序列中的探索是有效的(但序列不适合大量探索)。数组和序列通常用作更复杂对象的数据结构。
  • 地图是动态结构,其大小不断适应插入项目的数量,并且对项目的访问是最快的。地图结构通常用于大量探索的情况:它们通常是复杂算法的内部数据结构。
  • 列表类似于序列,但有不同的算法来探索它们。
  • 加速结构是基于局部性标准(如在 3D 中通过射线拾取对象)为快速遍历优化的树或其他结构。

这些类的宏定义存储在NCollection_Define*.hxx文件中。这些定义现在已经过时,但仍然可以使用,特别是为了与现有代码兼容。

让我们看一个 NCollection 模板类实例化头文件MyPackage_SequenceOfPnt.hxx中的点序列的示例(类似于TColgp_SequenceOfPnt):

#include < gp_Pnt.hxx >
typedef NCollection_Sequence<gp_Pnt> MyPackage_SequenceOfPnt;

对于这种情况,当序列本身应该由句柄管理时,可以使用辅助宏DEFINE_HSEQUENCE :

#include < gp_Pnt.hxx >
typedef NCollection_Sequence<gp_Pnt> MyPackage_SequenceOfPnt;
DEFINE_HSEQUENCE (MyPackage_HSequenceOfPnt, MyPackage_SequenceOfPnt)
Handle(MyPackage_HSequenceOfPnt) aSeq = new MyPackage_HSequenceOfPnt();

在以下部分中查看有关可用集合的更多详细信息。

数组和序列

OCCT 提供的标准馆藏有:

  • NCollection_Array1 – 固定大小(初始化时)一维数组;请注意,索引可以从任何值开始,通常是 1;
  • NCollection_Array2 – 固定大小(初始化时)二维数组;请注意,索引可以从任何值开始,通常是 1;
  • NCollection_List – 普通列表;
  • NCollection_Sequence – 通过索引访问的双连接列表;注意索引从 1 开始;
  • NCollection_Vector – 两步索引数组,大小可扩展,但不可收缩;
  • NCollection_SparseArray – 具有稀疏内存分配的类似数组的结构,用于具有不连续性的序列。

这些类提供 STL 风格的迭代器(方法 begin() 和end()),因此可以在 STL 算法中使用。

NCollection_Array1

这些是类似于 C 数组的一维数组,即大小固定但在构建时动态标注。与 C 数组一样,NCollection_Array1索引项的访问时间是恒定的,并且与数组大小无关。数组通常用作更复杂对象的基本数据结构。

此模板类依赖于Item,即数组中元素的类型。数组索引在给定类构造函数的位置开始和结束。因此,当访问一个项目时,您必须将索引基于数组的下限和上限。

NCollection_Array2

这些是固定大小的二维数组,但在构建时动态标注。

与 C 数组一样,NCollection_Array2索引项的访问时间是恒定的,并且与数组大小无关。数组通常用作更复杂对象的基本数据结构。

此模板类依赖于Item,即数组中元素的类型。数组索引在给定类构造函数的位置开始和结束。因此,当访问一个项目时,您必须将索引基于数组的下限和上限。

NCollection_List

这些是非唯一对象的有序列表,可以使用NCollection_List::Iterator顺序访问。列表中的项目插入在任何位置都非常快。但是如果列表很长,按值搜索项目可能会很慢,因为它需要顺序搜索。

这个模板类依赖于结构中元素的类型Item 。在按值搜索项目时,序列是一种更好的结构。队列和堆栈是其他类型的列表,具有不同的数据访问权限。

NCollection_Sequence

这是一个由整数索引的项目序列。序列的目标与一维数组(NCollection_Array1)大致相同:它们通常用作更复杂对象的基本数据结构。但是序列是可变大小的结构:序列避免使用大的和准空的数组。当探索按顺序进行时,探索序列数据结构是有效的;在其他地方,序列项的读取时间比数组项长。另请注意,当序列必须支持大量算法探索时,它们是无效的:地图对此更好。

这个模板类依赖于Item,即序列中元素的类型。序列中的第一个元素的索引等于 1。

NCollection_Vector

NCollection_Vector类在内部实现为相同大小的数组列表。其性质:

  • 直接(恒定时间)访问NCollection_Array1类型中的成员。数据分配在紧凑的块中,这提供了更快的迭代。
  • 可以无限制地增长,例如NCollection_ListNCollection_Sequence类型。
  • 一旦具有 LEN 大小,它就不能减少到任何小于 LEN 的大小——没有删除项目的操作。

在 Vector 类型的类中插入是通过两种方法进行的:

  • SetValue(ind, theValue) – 数组类型插入,其中 ind 是插入项的索引,可以是任何非负数。如果它大于或等于 Length(),则向量被放大(其 Length() 增长)。
  • Append(theValue) – 等同于myVec.SetValue(myVec.Length(), theValue)的列表类型插入,增加集合的大小。

来自NCollection_ListNCollection_Array1类型集合的其他基本属性:

  • NCollection_List一样, Clear()方法销毁所有包含的对象并释放分配的内存。
  • 就像在NCollection_Array1中一样, Value()ChangeValue()方法通过索引返回一个包含的对象。此外,这些方法具有重载 operator() 的形式。

向量中的第一个元素的索引等于 0。

NCollection_SparseArray

NCollection_SparseArray类具有与NCollection_Vector几乎相同的功能,但它允许存储具有分散索引的项目。在NCollection_Vector中,如果您设置索引为 1000000 的项目,容器将为索引在 0-1000000 范围内的所有项目分配内存。在NCollection_SparseArray中,只会保留一小块项目,其中包含索引为 1000000 的项目。

这个类也可以看作是NCollection_DataMap<int,TheItemType>的等价物,只有一个实际区别:如果项目很小(例如 Integer 或 Handle),它的内存消耗会少得多。

该类型具有NCollection_DataMapNCollection_Vector两个接口来访问项目。

map

OCCT 提供了几个按值存储对象的类,由于使用散列提供了快速搜索:

地图是动态扩展的数据结构,其中数据可以通过一个快速访问。一旦插入到地图中,地图项就会被引用为地图的条目。地图避免使用大型和准空数组。

映射的每个条目都由一个键寻址。两个不同的键寻址映射的两个不同条目。地图中条目的位置称为

地图由其桶数确定,即地图中的最大条目数。散列函数将键转换为桶索引。哈希函数可以计算的值的数量等于映射的桶数。

散列函数和两个键之间的相等性测试都由散列器对象提供。

映射项的访问时间比序列、列表、队列或堆栈项的访问时间要好得多。它与数组项的访问时间相当。它取决于映射的大小(桶数)和用户可重新定义的散列函数的质量。

键、项目哈希是这些 OCCT 映射模板的参数。NCollection_DefaultHasher类描述了任何hasher所需的函数,该函数将与从NCollection组件实例化的映射一起使用。

地图迭代器可以探索地图。这种探索只提供地图中的插入条目(即非空桶)。

NCollection_DataMap

这是用于存储键与关联项的映射。NCollection_DataMap的条目由键和项组成。NCollection_DataMap可以看作是一个扩展数组,其中键是索引。

NCollection_DataMap是一个模板类,它依赖于三个参数:

  • 是映射中条目的键类型,
  • Item是与地图中的键关联的元素类型,
  • 哈希器是键上的哈希器类型。

使用NCollection_DataMap::Iterator来探索NCollection_DataMap映射。NCollection_DefaultHasher类描述了Hasher对象所需的函数。

NCollection_DoubleMap

这是用于绑定键对 (Key1,Key2) 并在线性时间内检索它们的映射。

Key1被引用为NCollection_DoubleMap的第一个键,而Key2被引用为第二个键。

NCollection_DoubleMap的条目由一对两个键组成:第一个键和第二个键。

NCollection_DoubleMap是一个模板类,它依赖于四个参数:

  • Key1是映射中条目的第一个键的类型,
  • Key2是映射中条目的第二个键的类型,
  • Hasher1是第一个键上的哈希类型,
  • Hasher2是第二个键上的哈希类型。

使用NCollection_DoubleMap::Iterator探索NCollection_DoubleMap地图。NCollection_DefaultHasher类描述了Hasher1Hasher2对象所需的函数。

NCollection_IndexedDataMap

这是一个映射,用于存储具有关联项目的键并将索引绑定到它们。

存储在地图中的每个新键都分配有一个索引。索引作为存储在地图中的键(和项目)递增。一个键可以通过索引找到,一个索引可以通过键找到。除了最后一个键可以删除,因此索引在 1…Upper 范围内,其中Upper是存储在映射中的键数。每个键存储一个项目。

NCollection_IndexedDataMap的条目由键、项和索引组成。NCollection_IndexedDataMap是一个有序映射,它允许对其内容进行线性迭代。它结合了以下兴趣:

  • 数组,因为可以使用索引访问数据,
  • 和地图,因为数据也可以通过键访问。

NCollection_IndexedDataMap是一个模板类,它依赖于三个参数:

  • 是映射中条目的键类型,
  • Item是与地图中的键关联的元素类型,
  • 哈希器是键上的哈希器类型。

NCollection_IndexedMap

这是用于存储键并将索引绑定到它们的映射。

存储在地图中的每个新键都分配有一个索引。索引作为存储在映射中的键递增。通过索引可以找到键,通过键可以找到索引。除了最后一个键可以删除,因此索引在 1…Upper 范围内,其中 Upper 是存储在映射中的键的数量。

NCollection_IndexedMap的条目由键和索引组成。NCollection_IndexedMap是一个有序映射,它允许对其内容进行线性迭代。但是没有数据附加到密钥上。算法通常使用NCollection_IndexedMap来了解是否仍在对复杂数据结构的组件执行某些操作。

NCollection_IndexedMap是一个模板类,它依赖于两个参数:

  • 是映射中条目的键类型,
  • 哈希器是键上的哈希器类型。

NCollection_Map

这是一个基本的散列映射,用于在线性时间内存储和检索键。

NCollection_Map的条目仅由键组成。没有数据附加到密钥。算法通常使用NCollection_Map来了解是否仍在对复杂数据结构的组件执行某些操作。

NCollection_Map是一个泛型类,它依赖于两个参数:

  • Key是映射中键的类型,
  • 哈希器是键上的哈希器类型。

使用NCollection_Map::Iterator探索NCollection_Map地图。

NCollection_DefaultHasher

这是从NCollection组件实例化的映射上的默认哈希。

哈希器提供两个功能:

  • HashCode()函数将键转换为地图中的桶索引。哈希函数可以计算的值的数量等于映射中的桶数。
  • IsEqual是两个键之间的相等测试。

哈希器用作NCollection组件提供的模板映射中的参数。

NCollection_DefaultHasher是一个模板类,它依赖于键的类型,前提是Key是来自Standard包的类型。在这种情况下, NCollection_DefaultHasher可以直接用Key实例化。请注意,包TColStd提供了其中一些实例化。

在其他地方,如果Key不是Standard包中的类型,您必须将NCollection_DefaultHasher视为模板并构建一个包含其函数的类,以便在从NCollection组件实例化的映射中将其用作哈希器。

请注意,TCollection_AsciiStringTCollection_ExtendedString类对应于这些规范,因此它们可以用作散列器:当Key是这两种类型之一时,您可以在实例化地图时将散列器定义为相同的类型。

迭代器

每个集合都定义了它的Iterator类,它能够以某种预定义的顺序迭代成员。每个迭代器都被定义为特定集合类型的子类型(例如,MyPackage_StackOfPnt::Iterator)。迭代的顺序由特定的集合类型定义。

Iterator 的常用方法有:

Name Method Description
Init() void Init (MyCollection& ) 在集合对象上初始化迭代器
More() bool More() 查询是否有另一个非迭代成员
Next() void Next() 递增迭代器
Value() const ItemType& Value() 返回当前成员
ChangeValue() ItemType& ChangeValue() 返回可变的当前成员

使用示例:

typedef Ncollection_Sequence<gp_Pnt> MyPackage_SequenceOfPnt;
void Perform (const MyPackage_SequenceOfPnt& theSequence)
{
for (MyPackage_SequenceOfPnt::Iterator anIter (theSequence); anIter.More(); anIter.Next())
{
const gp_Pnt aPnt& = anIter.Value();
}
}

分配器

NCollection类的所有构造函数都接收Allocator对象作为最后一个参数。这是一个由 Handle 管理的类型的对象,继承NCollection_BaseAllocator,并重新定义了以下(强制)方法:

virtual void* Allocate (const size_t theSize) override;
virtual void Free (void* theAddress) override;

每次当集合为其项目分配内存并释放此内存时,它都会在内部使用。此参数的默认值(空Handle)指定使用NCollection_BaseAllocator,其中调用函数Standard::AllocateStandard::Free。因此,如果NCollection的用户没有指定任何分配器作为其集合构造函数的参数,则内存管理将与其他 Open CASCADE Technology 类相同。

然而,可以定义一个自定义的分配器类型来以最优化或最方便的方式管理该算法的内存。

作为一种可能的选择,包括NCollection_IncAllocator类。与NCollection_BaseAllocator不同,内存是按大块(大约 20kB)分配的,并且分配器会跟踪占用的内存量。Allocate方法只是增加指向未占用内存的指针并返回其先前的值。内存只在NCollection_IncAllocator的析构函数中释放,方法Free为空。如果使用得当,这个 Allocator 可以大大提高特定算法的性能。

加速结构

OCCT 提供了几种数据结构,用于基于对象的位置(在 3D 空间中)对大型对象集合的优化遍历。

NCollection_UBTree

类名NCollection_UBTree代表“不平衡二叉树”。它将成员存储在重叠边界对象(框或其他)的二叉树中。一旦构建了几何对象的框树,该算法就能够快速地对对象进行几何选择。树可以通过添加一个带有边界框的新对象来轻松更新。加入一个对象的树的时间是O(log(N)),其中N是对象的总数,所以构建N个对象的树的时间是O(N(log(N))。一个对象的搜索时间是 O(log(N))。

定义继承NCollection_UBTree::Selector的各种类,我们可以在同一个 b-tree 对象上执行各种选择。

对象可以是任何允许复制的类型。在最合适的解决方案中,可以有一个指向某个集合内的对象、已处理对象或对象的整数索引的指针。

边界对象可以具有任何尺寸和几何形状。NCollection_UBTree算法中使用的TheBndType的最小接口(除了 public empty 和 copy constructor 和 operator=)如下:

class MyBndType
{
public:
//! Updates me with other bounding type instance
void Add (const MyBndType& theOther);
//! Classifies other bounding type instance relatively me
Standard_Boolean IsOut (const MyBndType& theOther) const;
//! Computes the squared maximal linear extent of me (for a box it is the squared diagonal of the box).
Standard_Real SquareExtent() const;
};

此接口在 Bnd 包的类型中实现:Bnd_BoxBnd_Box2d、 Bnd_B2x 、 Bnd_B3x

要选择对象,您需要定义一个派生自NCollection_UBTree::Selector的类,该类应重新定义必要的虚拟方法以维持选择条件。通常此类实例也用于在搜索后检索选定的对象。NCollection_UBTreeFiller类用于随机填充NCollection_UBTree实例。如果以随机顺序将对象添加到树中,以避免添加一个接一个的附近对象链,则树的质量会更好(考虑到搜索速度)。NCollection_UBTreeFiller的实例化收集要添加的对象,然后立即将它们添加到给定的NCollection_UBTree使用 Fisher-Yates 算法以随机顺序排列实例。下面是创建NCollection_UBTree实例的示例代码,该实例由 2D 框 ( Bnd_B2f ) 索引,然后执行选择,返回其边界框包含给定 2D 点的对象。

typedef NCollection_List<MyData> ListOfSelected;
//! Tree Selector type
class MyTreeSelector : public UBTree::Selector
{
public:
//! This constructor initializes the selection criterion (e.g., a point)
MyTreeSelector (const gp_XY& thePnt) : myPnt(thePnt) {}
//! Get the list of selected objects
const ListOfSelected& ListAccepted() const { return myList; }
//! Bounding box rejection – definition of virtual method.
//! @return True if theBox is outside the selection criterion.
virtual Standard_Boolean Reject (const Bnd_B2f& theBox) const override { return theBox.IsOut (myPnt); }
//! Redefined from the base class.
//! Called when the bounding of theData conforms to the selection criterion.
//! This method updates myList.
virtual Standard_Boolean Accept (const MyData& theData) override { myList.Append (theData); }
private:
gp_XY myPnt;
ListOfSelected myList;
};
. . .
// Create a UBTree instance and fill it with data, each data item having the corresponding 2D box.
UBTree aTree;
for(;;)
{
const MyData& aData = …;
const Bnd_B2d& aBox = aData.GetBox();
aTreeFiller.Add (aData, aBox);
}
aTreeFiller.Fill();
. . .
// Perform selection based on “aPoint2d”
MyTreeSelector aSel (aPoint2d);
aTree.Select (aSel);
const ListOfSelected& aSelected = aSel.ListAccepted();

NCollection_CellFilter

NCollection_CellFilter类表示用于将 n 维空间中的几何对象分类为单元的数据结构,以及用于快速检查与其他对象的重合(重叠、相交等)的相关算法。它可以被认为是NCollection_UBTree的功能替代品,因为在最好的情况下,它提供了对 n 维数组中对象的直接访问,而使用NCollection_UBTree进行搜索提供对数定律访问时间。

标准对象的集合

TShortTColGeomTColGeom2dTColStdTColgp为标准 OCCT 类型提供NCollection模板的模板实例化(typedef) 。名称中带有H前缀的类是基于句柄的变体并继承Standard_Transient

TopTools这样的包还包括对复杂类型(如形状)的集合和哈希函数的定义—— TopTools_ShapeMapHasherTopTools_MapOfShape

除了该类之外,TColStd_PackedMapOfInteger提供了整数映射的替代实现,针对性能和内存使用进行了优化(它使用位标志对整数进行编码,这导致在最佳情况下存储的每 32 个整数仅花费 24 个字节)。此类还提供布尔运算,将映射作为整数集(并集、交集、减法、差异、检查相等性和包含性)。

字符串

TCollection_AsciiString定义了一个可变长度的 UTF-8 代码点序列(普通 8 位字符类型),而TCollection_ExtendedString存储 UTF-16/UCS-2 代码点(16 位字符类型)。两者都遵循值语义——也就是说,它们是实际的字符串,而不是字符串的句柄,并且通过赋值进行复制。TCollection_HAsciiString / TCollection_HExtendedString是TCollection_AsciiString / TCollection_ExtendedString上的句柄包装器。

字符串类提供以下服务来操作字符串:

  • 使用内置的字符串管理器编辑字符串对象的操作
  • 处理动态大小的字符序列
  • 从/到 ASCII 和 UTF-8 字符串的转换。

TCollection_AsciiStringTCollection_ExtendedString提供 UTF-8 <-> UTF-16 转换构造函数,使这些字符串类可以互换。Resource_Unicode提供了将 ANSI、EUC、GB 或 SJIS 格式的字符串转换为 Unicode 字符串的功能,反之亦然。NCollection_UtfIterator类在多字节 UTF-8/UTF-16 字符串上实现迭代器作为 UTF-32 Unicode 符号序列。

数量

数量是支持日期和时间信息和颜色的各种类。

数量类提供以下服务:

  • 单位转换工具提供了处理数量和相关物理单位的统一机制:检查单位兼容性、执行不同单位之间的值转换等(请参阅包UnitsAPI
  • 管理日期和时间段等时间信息的资源
  • 管理颜色定义的资源

数学量的特征是名称和值(实数)。

物理量的特征是名称、值(实数)和单位。单位可以是符合国际单位制 (SI) 的国际单位,也可以是用户定义的单位。单位由物理量用户管理。

单位换算

UnitsAPI全局函数用于将值从任何单位转换为另一个单位。在三个单位系统之间执行转换:

  • SI系统,
  • 用户的本地系统
  • 用户的当前系统

SI 系统是标准的国际单位系统。它在UnitsAPI函数的签名中由SI表示。

OCCT(前 MDTV)系统对应于 SI 国际标准,但长度单位及其所有派生单位使用毫米而不是米。

两种系统均由 Open CASCADE Technology 提出;SI 系统是标准选项。通过选择这两个系统之一,您可以通过SetLocalSystem函数定义您的本地系统。本地系统UnitsAPI函数的签名中由LS指示。本地系统单位可以在工作环境中修改。您可以通过SetCurrentUnit函数修改其单位来定义当前系统。当前系统在UnitsAPI函数的签名中由Current指示。物理量由字符串定义(例如:LENGTH)。

数学原语和算法

概述

Open CASCADE 技术中可用的数学原语和算法包括:

  • 向量和矩阵
  • 几何图元
  • 数学算法

向量和矩阵

Vectors and Matrices 组件提供了基本类型math_Vectormath_Matrix的 C++ 实现,它们通常用于定义更复杂的数据结构。

math_Vector和math_Matrix类提供常用的数学算法,包括

  • 涉及向量和矩阵的基本计算;
  • 计算方阵的特征值和特征向量;
  • 一组线性代数方程的求解器;
  • 找出一组非线性方程的根的算法;
  • 找到一个或多个自变量的最小函数的算法。

这些类还提供了一种数据结构来表示数学中使用的任何表达式、关系或函数,包括变量的分配。

向量和矩阵具有任意范围,必须在声明时定义,声明后不能更改。

math_Vector aVec (1, 3);
// a vector of dimension 3 with range (1..3)
math_Matrix aMat (0, 2, 0, 2);
// a matrix of dimension 3×3 with range (0..2, 0..2)
math_Vector aVec (N1, N2);
// a vector of dimension N2-N1+1 with range (N1..N2)

Vector 和 Matrix 对象使用值语义。换句话说,它们不能被共享并且可以通过分配来复制。

math_Vector aVec1 (1, 3), aVec2 (0, 2);
withVec2 = withVec1;
// aVec1 被复制到 aVec2 中;aVec1 的修改不会影响 aVec2

可以使用必须位于向量或矩阵的范围定义内的索引来初始化和获取向量和矩阵值。

math_Vector aVec (1, 3);
math_Matrix aMat (1, 3, 1, 3);
aVec (2) = 1.0;
aValue = aVec(1);
aMat (1, 3) = 1.0;
aValue = aMat (2, 2);

对 Vector 和 Matrix 对象的某些操作可能不合法。在这种情况下会引发异常。使用了两个标准例外:

  • 当运算中涉及的两个矩阵或向量的维度不兼容时,将引发Standard_DimensionError异常。
  • 如果尝试在向量或矩阵的范围定义之外进行访问,则会引发Standard_RangeError异常。
math_Vector aVec1 (1, 3), aVec2 (1, 2), aVec3 (0, 2);
aVec1 = aVec2; // 错误:引发 Standard_DimensionError
aVec1 = aVec3; // OK:范围不相等但尺寸兼容
aVec1 (0) = 2.0; // 错误:引发 Standard_RangeError

原始几何类型

Open CASCADE Technology 原始几何类型是基本几何和代数实体的 STEP 兼容实现。他们提供:

  • 原始几何形状的描述,例如:
    • 积分;
    • 载体;
    • 线条;
    • 圆和圆锥;
    • 平面和基本表面;
  • 通过轴或坐标系在空间或平面中定位这些形状;
  • 这些形状的几何变换的定义和应用:
    • 翻译;
    • 轮换;
    • 对称性;
    • 缩放变换;
    • 组合变换;
  • 代数计算的工具(坐标和矩阵)。

所有这些功能都由几何处理器包​​gp提供。它用于 2d 和 3d 对象的类是按值而不是按引用处理的。当这种对象被复制时,它被完全复制。一个实例中的更改不会反映在另一个实例中。

gp包定义了用于 2d 和 3d 空间中的代数计算和基本解析几何的基本几何实体。它还提供了基本的转换,例如身份、旋转、平移、镜像、缩放转换、转换组合等。实体由值处理。

请注意,gp曲线和曲面是解析的: gp实体没有参数化和方向,即这些实体不提供与这些属性一起使用的功能。

如果需要,您可以使用Geom(在 3D 空间中)和Geom2d(在平面中)提供的更进化的数据结构。但是,gp实体的定义与等效的GeomGeom2d实体的定义相同,它们位于平面或空间中,具有相同的定位系统。它们隐含地包含方向,它们在GeomGeom2d实体上表达,并且它们引发了参数化的定义。

因此,很容易对gp曲线和曲面进行隐式参数化,即等价的GeomGeom2d实体的参数化。此属性在计算投影或交点时特别有用,或者对于涉及复杂算法的操作特别有用,其中操作最简单的数据结构(即gp的数据结构)尤为重要。因此,ElCLibElSLib包提供了计算函数:

  • 参数 u 在 2D 或 3D gp 曲线上的点,
  • gp 基本面上的参数 (u,v) 点,以及
  • 此时的任何导数向量。

注意:gp实体在更复杂的数据结构中时不能共享。

原始几何类型的集合

在创建几何对象之前,您必须确定您是在 2d 还是 3d 上下文中,以及您希望如何处理该对象。如果您不需要几何图元的单个实例而是一组它们,那么处理此类对象集合的包TColgp将提供必要的功能。特别是,这个包提供了具有几何对象的通用类的标准和常用实例,即gp_XYgp_XYZgp_Pntgp_Pnt2dgp_Vecgp_Vec2dgp_Lingp_Lin2dgp_Circgp_Circ2d

基本几何库

有各种可用的库包,它们提供了一系列关于曲线和曲面的基本计算。如果您正在处理从gp包创建的对象,有用的算法在基本曲线和曲面库中 – ElCLibElSLib包。

  • EICLib提供解析曲线的方法。这是一个来自gp包(直线、圆和圆锥)的曲线简单计算库。可以使用给定参数计算点或计算点的参数。
  • EISLib提供了分析曲面的方法。这是来自gp包(平面、圆柱体、球体、圆锥体、托里)的表面简单计算库。可以使用给定的参数对计算点或计算点的参数。有一个用于计算曲线和曲面法线的库。

此外,Bnd包提供了一组类和工具来操作 2d 和 3d 空间中几何对象的边界框。

常用数学算法

通用数学算法库提供最常用数学算法的 C++ 实现。这些包括:

  • 求解一组线性代数方程的算法,
  • 找到一个或多个自变量的函数的最小值的算法,
  • 找到一个或一组非线性方程的根的算法,
  • 一种求方阵特征值和特征向量的算法。

所有数学算法都使用相同的原理实现。他们包含:

  • 在给定适当参数的情况下,执行所有或大部分计算的构造函数。所有相关信息都存储在生成的对象中,以便以最有效的方式解决所有后续计算或询问。
  • 如果计算成功,函数IsDone返回布尔值 true。
  • 一组函数,特定于每个算法,能够获得所有不同的结果。仅当函数IsDone回答true时调用这些函数才是合法的,否则引发异常StdFail_NotDone

下面的示例演示了math_Gauss类的使用,该类实现了一组线性方程的高斯解。以下定义摘自math_Gauss类的头文件:

{
public:
void Solve (const math_Vector& B, math_Vector& X) const;
};

现在主程序使用math_Gauss类求解方程a*x1=b1a*x2=b2

#include <math_Vector.hxx>
#include <math_Matrix.hxx>
main()
{
math_Vector a(1, 3, 1, 3);
math_Vector b1(1, 3), b2(1, 3);
math_Vector x1(1, 3), x2(1, 3);
// a, b1 and b2 are set here to the appropriate values
math_Gauss aSol(a); // computation of the LU decomposition of A
if (aSol.IsDone()) // is it OK ?
{
aSol.Solve(b1, x1); // yes, so compute x1
aSol.Solve(b2, x2); // then x2
}
else // it is not OK:
{
// fix up
aSol.Solve(b1, x1); // error:
// StdFail_NotDone is raised
}
}

下一个示例演示了math_BissecNewton类的使用,该类实现了 Newton 和 Bissection 算法的组合,以找到已知位于两个边界之间的函数的根。该定义是从类math_BissecNewton的头文件中提取的:

抽象类math_FunctionWithDerivative描述了必须为math_BissecNewton算法使用的函数f实现的服务。以下定义对应抽象类math_FunctionWithDerivative的头文件:

现在测试样本使用math_BissecNewton类在区间 [1.5, 2.5]中找到方程f(x)=x**2-4的根。要求解的函数在类myFunction中实现,该类继承自类math_FunctionWithDerivative,然后主程序找到所需的根。

class myFunction : public math_FunctionWithDerivative
{
Standard_Real myCoefA, myCoefB, myCoefC;
public:
myFunction (const Standard_Real theA, const Standard_Real theB, const Standard_Real theC)
: myCoefA(a), myCoefB(b), myCoefC(c) {}
virtual Standard_Boolean Value (const Standard_Real x, Standard_Real& f) override
{
f = myCoefA * x * x + myCoefB * x + myCoefC;
}
{
d = myCoefA * x * 2.0 + myCoefB;
}
{
f = myCoefA * x * x + myCoefB * x + myCoefC;
d = myCoefA * x * 2.0 + myCoefB;
}
};
main()
{
myFunction aFunc (1.0, 0.0, 4.0);
math_BissecNewton aSol (aFunc, 1.5, 2.5, 0.000001);
if (aSol.IsDone()) // is it OK ?
{
Standard_Real x = aSol.Root(); // yes
}
else // no
{
}

精度

在 OCCT 平台上,存储在数据库中的每个对象都应该带有自己的精度值。在处理从其他系统导入对象的系统以及各种相关的精度值时,这一点很重要。

Precision包解决了几何算法开发人员的日常问题:使用什么精度设置来比较两个数字。实数等价显然是一个糟糕的选择。应将数字之间的差异与给定的精度设置进行比较。

不要写if (X1 == X2),而是写if (Abs(X1-X2) < Precision )

此外,要订购实数,请记住如果 (X1 < X2 – Precision )不正确。当X1X2是大数字时, if (X2 – X1 > Precision )要好得多。

这个包提出了一组方法,为最常见的情况提供精确设置。

在 Open CASCADE 技术中,精度通常不是隐含的;低级几何算法接受精度设置作为参数。通常这些不应该直接引用这个包。

高级建模算法必须为其调用的低级几何算法提供精确设置。一种方法是使用此包提供的设置。高级建模算法也可以有自己的精度管理策略。作为示例,拓扑数据结构存储稍后由算法使用的精度值。创建新拓扑时,它会使用存储的值。

该软件包提供的不同精度设置涵盖了几何算法的最常见需求,例如IntersectionApproximation。精度值的选择取决于算法和几何空间。几何空间可以是:

  • 一个真实的空间,3d 或 2d,长度以米、微米、英寸等为单位。
  • 参数空间,曲线上的 1d 或数字没有维度的曲面上的 2d。

参数空间精度值的选择不仅取决于机器的精度,还取决于曲线或曲面的尺寸。这是因为希望将参数精度和实际精度联系起来。如果您位于由方程P(t)定义的曲线上,则您希望在以下之间具有等价性:

Abs (t1 – t2) < ParametricPrecision
Distance (P(t1), P(t2)) < RealPrecision

精度封装

Precision包提供了许多包方法和默认精度,用于处理角度、距离、交叉点、近似值和参数空间。它提供了用于比较测试实数相等性的值。

  • 角度精度比较角度。
  • 混淆精度比较距离。
  • 交叉点算法使用交叉点精度。
  • 近似算法使用近似精度
  • 参数精度从 3D 精度获得参数空间精度。
  • Infinite返回一个可以被认为是无限的高数。使用-Infinite表示较大的负数。

标准精度值

这个包为算法提供了一组实空间精度值。实际空间精度设计精度为0.1纳米(如果模型以毫米为单位定义)。

参数精度是通过参数函数从实际精度推导出来的。这将应用一个比例因子,它是曲线或曲面的切线长度。你,用户,提供这个长度。具有[0,1]参数空间且长度小于 100 米的曲线有一个默认值。

几何包为不同类型的曲线提供参数精度。Precision包提供了测试一个实数是否可以被认为是无限的方法。

精度::角度

此方法用于比较两个角度。它的当前值为Epsilon(2 * PI)即最小的数x使得2*PI + x不同于2*PI

它可用于检查两个角度的混淆,如下所示:

bool areEqualAngles (double theAngle1, double theAngle2)
{
return Abs(theAngle1 – theAngle2) < Precision::Angular();
}

也可以检查两个向量的并行性,如下所示:

bool areParallelVectors (const gp_Vec& theVec1, const gp_Vec& theVec2)
{
return theVec1.IsParallel (theVec2, Precision::Angular());
}

请注意,Precision::Angular()可用于点积和叉积,因为对于小角度,正弦角度是等价的。所以要测试gp_Dir类型的两个方向是否垂直,使用下面的代码是合法的:

bool arePerpendicular (const gp_Dir& theDir1, const gp_Dir& theDir2)
{
return Abs(theDir1 * theDir2) < Precision::Angular();
}

精度::混乱

此方法用于测试 3D 距离。当前值为1.e-7,换句话说,如果使用的单位是毫米,则为 1/10 微米。

它可以用来检查以下两点的混淆:

bool areEqualPoints (const gp_Pnt& thePnt1, const gp_Pnt& thePnt2)
{
return thePnt1.IsEqual (thePnt2, Precision::Confusion());
}
~~~
It is also possible to find a vector of null length:
~~~~{.cpp}
bool isNullVector (const gp_Vec& theVec)
{
return theVec.Magnitude() < Precision::Confusion();
}

精度::交集

这是传递给交叉点过程的合理精度,作为交叉点细化的限制。交点足够高,可以使过程快速收敛。Intersection低于Confusion,因此您仍然可以在相交的几何图形上获得一个点。当前值为Confusion() / 100

精度::近似

这是一个合理的精度,可以作为拟合细化的限制传递给近似过程。该近似值大于其他精度,因为它被设计为在时间非常宝贵时使用。近似算法的设计者已将其作为合理的折衷方案提供。当前值为Confusion() * 10请注意,Approximation 大于 Confusion,因此在近似过程中使用 Confusion 时必须小心。

© 版权声明
THE END
喜欢就支持一下吧
点赞1229 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容