C++/CLI 新的语法
可能我们中有不少人都发现,要在前两个版本的C++中使用托管扩展语法,会非常麻烦并且错误重重,也许还会觉得,Visual C++并非是 .NET
开发的首选语言。微软Visual C++开发小组在广泛听取意见的基础上,重新改进了Visual C++ 2005,在Visual Studio .NET 2002中那种笨拙的C++托管扩展语法已经一去不复返了,而修订后的语言定义带来的是一个完全充满吸引力的新语法。
在语言设计上,微软Visual C++开发小组制定了一些主要的目标。首先(对那些认为编程是
艺术的人来说,也许是最重要的),他们要保证在开发者在编写C++代码时感觉自然,为达到这个目的,他们对ISO C++标准作了一个在语法上优雅的纯粹扩展,目的是为了在像单击部署、窗体
设计支持和SQL Server 2005的托管代码支持这些地方,可更简单地用C++编写可验证的代码。他们想要设计出一个超过C++的语言,要带给C++全部的 .NET能力,同时也要带给 .NET全部的C++能力。现在看来,他们非常的成功。
新的扩展规范被称为“C++/CLI”,并且正在被标准化。在阅读代码时,最引人注意之处就是,以往那种在托管扩展中定义垃圾回收类、属性等等常用到的双下划线关键字,现在已成为了历史。虽然也有一些类似的关键字被保留,但由于不会被经常用到,所以不会对代码的可读性造成影响。这些双下划线关键字如今被两种新的“关键字”取代:“上下文敏感”和“空隔”。“上下文敏感”关键字是只在特定的上下文中,才是关键字;而“空隔”关键字只在联合其他关键字时,才会是关键字。例如:在托管扩展中的关键字__property已被关键字property取代(不只是这样,定义和访问属性的整个语法都被精炼了,它的声明与C#看起来很相似,参见插1),而且在你的代码中,还可以使用property作为变量名,只有在一个类型中声明属性时,“property”才会被当作一个关键字。
代码1:语法比较
托管扩展语法
public __gc __sealed class Student { private: double m_grade; String* m_name; public: __property double get_Grade() { return m_grade; } __property void set_Grade(double newGrade) { m_grade = newGrade; }
__property String* get_Name() { return m_name; } __property void set_Name(String* newName) { m_name = newName; } } |
C++/CLI语法
public ref class Student sealed { private: double m_grade; public: // 标准属性语法 property double Grade { double get() { return m_grade; } void set(double newGrade) { m_grade = newGrade; } } //其他属性 property String^ Name; } |
在新语法中的类型被声明为“什么的类”,前置的形容词描述了你将创建什么样的类,如下所示:
class N { /*…*/ }; //本地类型 ref class R { /*…*/ }; // CLR引用类型 value class V { /*…*/ }; // CLR值类型 interface class I { /*…*/ }; // CLR界面接口类型 enum class E { /*…*/ }; // CLR枚举类型 |
在前一个语言版本中,声明类型的方法,表明了它生存和工作的方式。只有本地类、结构和托管值类型才可以创建在堆栈中,托管引用类始终在托管堆中。在Visual C++ 2005中,所有的类型,不管是本地还是托管,通过使用基于堆栈的确定性清理语义,都能被创建在堆栈中。
为在本地堆中生成类型T的一个对象实例,可使用“new T”,它返回一个指向本地堆中对象位置的指针(在Visual Studio .NET 2002和Visual Studio .NET 2003中为__nogc)。而在托管堆中生成类型T的一个对象实例,Visual C++ 2005引入了关键字gcnew,“gcnew T”将返回整个对象在托管堆中的句柄。句柄在Visual C++ 2005中被当作一个新的结构,有点类似于托管扩展的__gc指针;如今要在堆栈上生成一个类型T的实例,标准的“T t”声明就足够了。
在此说明一下是如何定义一个实例的,托管引用类建立于托管堆中,本地类型建立于堆栈或本地堆中,当一个托管引用类被声明建立在堆栈中时,编译器实际上还是在托管堆中把它实例化,如图1:
图1: |
图1中所示的托管引用类型也带来了一些问题:当一个堆栈中的实例对象超出了范围,将会有什么问题发生呢?它是怎样被清理的呢?许多C#的开发者抱怨C#语言缺乏确定性清理,C#语言可通过使用关键字,非常容易地清除IDisposable的对象,但这需要额外的代码,并且与C++开发者熟悉的析构模式相比,显得有点晦涩。在C#中,安全清理在默认状态下是关闭的,要打开它,还需要显式编码。举例来说,看一下插3中的C#代码片断,对象StreamReader声明在托管堆中,当类中的方法执行完毕后,对StreamReader的实例就不再有任何引用了,但是这个对象仍然不会结束,直到垃圾回收器回收它;而且,文件不会被关闭,
程序依然拥有打开文件的句柄。要加入确定性清理,必须通过那些使用非托管资源的类来实现IDisposable接口。
代码2中的第二个示例,演示了C#中新的代码,此处的代码仍具有不错的可读性,但一旦引入更多需要清理的对象时,代码就会变得越来越难读懂了,同样,当垃圾回收器最终运行时,任何忘记清除的对象,都会加重最终结束器线程(finalizer)的负担,同时,还有可能锁住高价值资源。而且,当用Visual Basic .NET来实现同样的功能时,这种编码形式会变得更加丑陋(虽然Visual Basic 2005也有一个类似于C#的Using声明)。
代码2:确定性清理代码
实现代码
C#(没有确定性清理)
string ReadFirstLineFromFile(string path) { StreamReader reader = new StreamReader(path); return reader.ReadLine(); }) |
C#(有确定性清理)
string ReadFirstLineFromFile(string path) { using (StreamReader reader = new StreamReader(path)) { return reader.ReadLine(); } } |
Visual Basic .NET(有确定性清理)
Function ReadFirstLineFromFile( _ByVal path As String) As String Dim reader As StreamReader Try reader = New StreamReader(path) ReadFirstLineFromFile = reader.ReadLine() Finally If Not reader Is Nothing Then _ CType(reader, IDisposable).Dispose() End Try End Function |
C++(有确定性清理)
String^ ReadFirstLineFromFile(String^ path) { StreamReader reader(path); return reader.ReadLine(); } |
现在,Visual C++ 2005对任何类型的托管和本地对象,都提供了一个析构函数或结束器(finalizer)。当类型是托管时,编译器映射一个析构函数到IDisposable::Dispose方法中,这意味着,你可用C++来编写同样的方法——如插3中的第四段代码,而且reader的析构或清除方法会被自动调用,就好像在C#中使用“using”一样。这样,当创建在堆栈上的类型超出作用范围之后,它的析构函数就会被调用。
托管扩展带来的最大问题就是指针问题了,虽然指针很难理解,但却是应付多种任务和多种情况的“多面手”。在Visual C++ 2005中,指针仍旧是老式的C++指针,它指向一个对象,并能执行一些算法。引用一个对象的指针,它的生存期必须由开发者显示地管理,当与指针打交道时,运行时库可不负责清理它。
现在,来看一下Visual C++ 2005的设计者是怎样实现的吧,Visual Studio .NET 2003和Visual Studio .NET 2005中的new操作符通常返回一个指针,而gcnew操作符返回一个“句柄”,一个用脱字符 ^ 语法表示的一个结构,此句柄指向托管堆的对象。因此,它们不能指向interior类型,而且在用法上,编译器也作了不少限制,以便开发者正确、安全地使用它们。句柄不能执行指针算法,也不能转换成一个空指针或任何其他整数类型,话说回来,依然可使用星号(*)和箭号(->)操作符。
这不是说,你再不能取得一个指向垃圾回收堆中的指针了,在Visual C++ 2005中的pin_ptr,可用于取得托管堆中对象的一个固定指针,只要这个指针存在,对象就被固定在托管堆中,以防止垃圾回收器清除它;Visual C++ 2005同时也引入了“引用跟踪”操作符,以百分号 % 表示。当年在C++中引入 & 引用操作符时,大多数开发者把它理解成一个指向对象的指针,而且由编译器自动解引用。在很多方面来说,% 之于 ^,就像 & 之于 *。
在托管世界中,对托管对象的本地引用,与指向托管对象的本地指针一样危险,指针和引用的基本原理在于,被引用的对象不能四处移动。引用跟踪与本地引用非常类似,除了它引用的对象是在托管堆中,而且被垃圾回收器移动之后,还能继续跟踪它们。百分号 % 操作符用来取托管对象的地址,就像 & 操作符对于本地对象的功能一样,百分号 % 操作符可返回一个托管引用类型对象的句柄。
一般来说,C++开发者知道ISO标准控制着语言的发展方向,正是因为这个原因,为了提高被第三方采用的机率,同时保证语言稳步向前发展,这种新语法被提议为一种称作“C++/CLI”的标准。在2003年10月,ECMA(欧洲计算
机制造商协会)投票决定建立一个特别工作组——TG5,专门负责分析和采用这个标准,其工作性质就像WG21是ISO C++的管理单位一样,实际上,WG21中的关键组员也同时
服务于TG5中。计划在2004年底,把C++/CLI标准化。