存在的问题?
位于托管堆中的任何对象在垃圾回收器进行清扫收缩的过程中都有可能遭遇重新分配,指向这些对象的任何指针必须被追踪并在运行时得到更新,而程序员 无法自己手动追踪它们,因此,如果你被允许用某个可能在托管堆中的值类型的地址,那么除了本机指针外,还需要一个追踪形态的指针。
到底该怎样去权衡呢?一方面,需要考虑简洁和安全。直接引入对一个或一组追踪指针的支持会使语言变得更复杂。如果不提供这种支持,由于所需的复杂程度降低,从而可以找到的程序员人群就会增加。此外,允许程序员访问这些生命期短暂的值类型,则增加了程序员出错的可能性。她经意或不经意地对内存做一些危险动作。不支持追踪指针,可以潜在地创建较安全的运行时环境。
另一方面,必须考虑效率和灵活性。每次将值类型赋值给相同的对象,该值都会发生新的框入/框出操作。允许访问这种经过框入/框出操作的值类型 ,就允许在内存中进行更新操作,这样便可能提供重要的性能改进。没有某种形式的追踪指针,你将无法用指针算法遍历CLI数组,这意味着CLI数组将不能 融入STL(标准模板库)中的迭代器模式,也无法与泛型算法协同工作。允许访问框入/框出值类型将会大大提高设计的灵活性。
在C++/CLI 中,微软选择提供一系列在托管堆中处理值类型的寻址模式:
int ival = 1024; int^ boxedi = ival;
array<int>^ ia = gcnew array<int>{1,1,2,3,5,8}; interior_ptr<int> begin = &ia[0];
value struct smallInt { int m_ival; ... } si; pin_ptr<int> ppi = &si.m_ival; |
典型的 C++/CLI 开发人员是一个经验丰富的系统程序员,其任务是提供底层架构以及作为基础的核心应用,以此为基础来构建未来。她必须解决可伸缩性和性能相关的问题,并且必须从系统一级来看待底层 CLI。某种 CLI 语言的细节标准反映了其程序员的面貌。
复杂性本身并不是对质量的否定,人类生命比单核细胞复杂得多,这当然不是一件坏事,然而,当单一概念的表达变得复杂化以后,这常常被认为是一件坏事。在C++/CLI中,CLI开发团队已经 尝试提供一种优雅的方式来表达一个复杂的主体。
附加功能
第三个设计层面是特定语言层功能要超过被CLI直接支持的功能,这样就需要建立一种语言层支持与CLI底层实现模型之间的映射。 在某些情况下,这是做不到的,因为该语言无法调解CLI的行为,在基类的构造函数和析构函数中解决虚函数便是例子。为了在这种情况中反映ISO-C++语义,需要在每个基类的构造函数和析构函数中 重新安排虚表。这是不可能的,因为虚表操作是由运行时托管的,而非单独的语言托管。 因此,这一设计层面是优越性和可行性的折中。C++/CLI 提供的附加功能主要有三个方面:
引用类型的资源获取(Resource Acquisition)形式是Initialization(RAII), 尤其是为被称作占据稀有资源的垃圾回收类型确定性终止化(deterministic finalization)提供一个自动化的机制;
与C++拷贝构造函数和拷贝赋值操作符相关的深度拷贝语义形式,但它不能扩展到值类型;
除了 CLI泛型机制之外——这原来是我第一个专栏的主题,还为CTS类型提供C++模板的直接支持,另外,还提供用于 CLI 类型的 STL 可验证版本;
让我们看一个简单的例子:确定性终止化问题。与对象关联的内存被垃圾回收器回收之前,若存在与之相关连的 Finalize 方法,该方法将会被调用。你可以把 该方法看作是一种超级析构函数,因为它不依赖于该对象程序的生命期,它被称为终止化。调用 Finalize 方法的时间,甚至是否调用它是未定义的。这就是垃圾回收器不确定的终止化操作含义之所在。
不确定性终止化在进行动态内存管理时可以很有效地工作,当可用内存空间严重不足时,垃圾回收器会发挥作用并解决问题。但是当对象涉及的是某些重要资源,比如数据库连接、某种 类型的锁、本地堆内存时,不确定性终止化的表现却不尽人意。在这种情况下,最好是尽快释放不再需要的资源。目前CLI采用的解决办法是:某个类在其 IDisposable 接口的 Dispose 方法中释放资源,这里的问题是 Dispose 需要显式调用,因此它不可能被执行。
C++的基本设计模式是前述的资源获取(Resource Acquisition )即初始化(Initialization),它意味着类通过构造函数 获取资源,相反,通过析构函数来释放资源。在类对象的生存期内是自动管理的。
以下是引用类型释放资源的过程:
用析构函数压缩在释放资源过程中必须的代码;
自动调用绑定到类对象生存期的析构函数;
CLI中,引用类型的类没有类析构的概念,因此,析构函数被映射到底层实现中另外的东西上,编译器则在内部完成如下转换: 类具备其基类列表,从接口 IDisposable 延伸继承;
析构函数被转换成IDisposable 的 Dispose 方法;
这仅仅完成了一半,还需要一种析构函数的自动调用途径。支持引用类型专用的基于堆栈的符号,也就是说其生命期与其声明的范围相关联。编译器 在内部转换符号,在托管堆中分配引用对象。随着范围的终止,编译器插入一个对 Dispose 方法的调用——用户定义的析构函数。与该对象关联的实际内存的回收仍然在垃圾回收器的掌控之下。例如如 Figure 1 所示。
C++/CLI 不仅仅是C++到管理世界的扩展,相反,它表现了一种完全的编程范例,类似于早期多重继承和泛型编程范例集成到该语言一样,我认为这个团队完成了一项杰出的工作。
那么,你是如何看待 C++/CLI 的呢?
C++/CLI代表了本地和托管编程的综合,在这个反复过程中,这种综合通过即独立而又等同的源码级和二进制元素共同体来完成,包括混合模式(本机和CTS类型的源码级混合,以及本机和CIL对象文件的二进制混合), 本地类型和CTS类型的混合,新增了混合本地对象和CIL对象的二进制文件),纯模式(本机和CTS类型的源码级混合,所有编译过的 CIL 对象文件),本机类(仅通过专门的包装类才可以操控 CTS 类型),以及 CTS 类(只能以指针形式操控本机类型)。
当然,C++/CLI 程序员也可以选择单独用 CLI 类型来编程,在这种方式中提供能被寄宿的可验证代码,比如 SQL Server 2005 中的存储过程。
现在,回到什么是 C++/CLI 的问题,它是进入.NET编程模型的第一道门槛,有了 C++/CLI,你不仅具备了迁移 C++ 源代码库的途径,同时还可以迁移 C++ 专业技术。这让我感觉非常惬意。 
2/2 首页 上一页 1 2 |