存在的问题
在垃圾收集器扫描紧缩状态下,位于托管堆上的任何对象非常可能面对重新定位问题。指向对象的指针可以实时跟踪并修改。开发人员不能自己手动跟踪,所以,如果你获许取得一个可能位于托管堆上的值类型的地址时,除了本地指针外,还需要有一个跟踪形态的指针。
销售商考虑的是什么?那就是需要简单和安全,在语言中直接提供跟踪一个对象或集合的指针使语言复杂化,没有这种支持,将减少复杂程度,可资利用的、潜在的程序开发人群可能会增加,此外,准许程序开发人员操作生命短暂的值类型,增加了错误产生的可能性,程序开发人员可能有意无意地对内存进行错误操作,不支持跟踪指针,一个潜在的更安全地实时环境产生了。
另一方面,效率和灵活性也是必须考虑的一个问题,每一次向同一个对象分配值类型时,一个全新的数值加箱操作发生了,准许存取加箱值类型允许在内存中进行更新,这可能在性能上产生了一个非常巨大的进步。没有跟踪形态的指针,你无法用指针算法重新声明一个CLI数组,这意味着CLI数组不能使用标准模板库进行重新声明,也不能使用一般的算法。准许操作加箱数值使设计具有更大地灵活性。
微软在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开发人员是一个复杂的系统程序员,承担着提供下层内部构造和有组织的应用程序的任务,而这些恰恰是未来商业发展的基础。C++/CLI开发人员必须兼顾可测量性和可执行性,所以必须在系统的高度级上来看待CLI下层结构。CLI细节水平反映了开发人员的脸色。
复杂性本身并不代表对质量的否定,人类比单细胞细菌复杂的多,这当然不是一件坏事,然而,当表达一个简单的概念变的复杂化后,这常常被认为是一件坏事。在C++/CLI中,CLI开发团队已经试着提供一种精巧的方法来表达方式一个复杂的事情。
额外增加的功能 第三个设计方面是特定功能性的语言层,它远远超过CLI所提供的直接支持,虽然这可能需要在语言层支持和CLI底层执行模式间建立一个映射。但在某些情况下,这恰恰是不可能的,因为语言无法调节CLI的行为。这种情况的例子就是在基类的构造及析构函数中定义虚函数。根据ISO-C++在这种情况下的语言学,需要用每一个基类的构造和虚构函数重新设置虚拟表,而这是不可能的,因为虚拟表句柄是实时管理的,而不是某一个语言来管理。
所以,这个设计方面是在完美性和可行性之间的妥协产物,C++/CLI提供的额外功能主要表现在三个方面:
1、获取资源的一种形式是对于引用类型的初始化,此外,提供一种自动化工具,用于占用较少资源、所谓的可确定性自动消亡的垃圾收集类型对象。
2、一种深度拷贝形式的语法与C++拷贝构造函数和拷贝分配操作符相一致,但其并不适用与值类型。
3、除了最初的一般性CLI机制外,还有对于CTS类型的C++模板直接支持。这些是我第一篇文章中讨论的主题。此外,还提供了针对CLI类型的可校验STL版本。
让我们来看一个简单的例子,一个确定性消亡问题。在垃圾搜集器重新声明一块与对象相关联的内存之前,一个相关的消亡方法,如果存在的话,将被调用。你可以认为这种方法是超级析构函数,因为它与对象的程序生命期无关。这就叫做终结。终结函数是否调用以及什么时间调用都没有明确规定,这就是垃圾收集器的非确定性终结。
在动态内存管理的情况下,非确定性终结工作非常好,当可用内存变的越来越少时,垃圾收集器介入并开始着手解决问题。然而,非决定性终结也有工作不好的时候,当一个对象维护一个重要资源,例如一个数据库连接、锁定某些类别、或者可能是本地的堆内存。在这种情况下,只要是不需要,应立即释放资源。目前CLI所支持的解决问题的方法是,对于一个类通过执行IDisposable接口提供的Dispose方法释放资源。这里的问题是执行Dispose方法需要一个清晰的声明,所以它也就不可能存在调用。
最基本的C++中的设计模式是上述的通过初始化来获取资源,这意味着类使用构造函数来获取资源,相反,类使用析构函数来释放资源。这些行为由类对象在生存期内自动管理。
下面是引用类释放资源时所做的顺序动作:
1、 首先使用析构函数来封装所有与释放类有关的资源时所必须的代码;
2、 析构函数自动调用后,结束类对象的生命期。
对于引用类型来说,CLI没有类析构函数的概念,所以析构函数不得不映射为在底层执行的其它代码。此时,在内部,编译器执行以下操作:
1、 类让其基类列表继承自IDisposable接口;
2、 析构函数转换成IDisposable的Dispose方法。
以上实现了目标的一半,一种实现析构造函数自动调用的方法仍然需要,对于引用类型,一种特殊的基于栈的符号得到支持,也就是说,一个对象的生命期与它的声明范围有关。在内部,编译器将符号转换为在托管堆上分配引用对象。随着作用域的终结,编译器插入一个Dispose方法-用户定义的析构函数。与对象有关的内存的收回在垃圾收集器的控制下得到执行。
C++/CLI并不是将C++拓展到一个托管的世界,更确切的说,它代表一个完全综合的范例,某种程度上就象当初将泛编程模式和多重继承综合进该语言一样。我认为C++/CLI开发小组做了一项非常卓有成效的工作。
小结
C++/CLI代表托管和本地编程的结合。在反复过程中,这种综合已经通过源级相对独立但又相互平等地组件和二进制元素得到了完成,包括混合模式(本地和CTS类型的源级混合,还有一个本地及CLI对象文件的二进制混合),纯模式(本地和CTS类型的源代码级混合,所有的都被编译为CLI对象文件),本地分类(可以通过一个特定的打包类来保持CTS类型),和CTS分类(可以保持本地类型为指针)。
当然,C++/CLI开发人员也可以单独使用CLI类型来编程,并通过这种方式来提供伺服状态下的可校验代码,例如可以作为SQL Server2005的一个SQL存储过程。
现在,还是回到这个问题上来,什么是C++/CLI?它是进行.NET编程模式的最佳切入点。对于C++/CLI,有一个来自C++的迁移路径,它不仅包含C++的底层基础,而且也需要C++编程经验,对于这些,我感到非常满意。
查看本文来源