对于C++语言的爱好者来说,Visual Studio .NET 2003中C++编译器的引入绝对令人垂涎欲滴。Visual C++ .NET 2003中有98%的部分与ISO C++标准保持一致,这使它比以往任何版本更为靠近这些标准,而且它还加入了对一些功能(如局部模板专用化)的语言支持。它还包括增强的缓冲区安全检查和改进的编译器诊断功能。C++开发人员就像C#和Visual Basic .NET开发人员一样,可以使用拖放窗体设计器来构建健壮的Windows窗体应用程序。该编译器还包含了针对Intel Pentium 4和AMD Athlon处理器的优化。
如果您对Visual C++ .NET 2003感到兴奋不已,您将会更加疯狂地爱上它的下一个版本Visual C++ 2005.Visual C++ 2005为。NET开发提供了既优雅又强大的新语法支持。全新的优化技术已经使Microsoft产品的运行速度提高了30%.它通过新的编译模式来确保Microsoft .NET Framework通用语言基础结构(Common Language Infrastructure,CLI)的一致性和可验证性,并且具有新的互操作(interop)模型,这不仅提供了本机和托管环境的无缝融合,而且还在跨边界的情况下提供了完全控制。该编译器增强了前两个版本中提供的缓冲区安全检查选项,并且还包括了C++应用程序普遍使用的以安全性为中心的库的新版本。它提供了对OpenMP标准以及64位平台(其中包括Intel Itanium和AMD64芯片)的支持。它解决了混合DLL加载问题,并且提供了对Double P/Invoke性能问题的运行时的自动清除。还可以列出许多增强和改进。正如C++小组的一位架构师告诉我的,“兄弟,C++总算找到了属于自己的位置!”
C++/CLI新的语法
在我们这些人中,有多少人讨厌使用前两个版本C++的托管扩展语法并且认为其中尽是错误?有多少人认为Visual C++没有被当作基于。NET的头号语言?很明显,当中的大多数人都是这样的(其中包括开发团队本身,只要阅读一下他们的Blog就知道了)。Visual C++小组的人听到了这些抱怨,于是开始开发Visual C++.与Visual Studio .NET 2002一起引入的C++语法的托管扩展就像恐龙一样消失殆尽了,因为引入了修订的语言定义,从而产生了一种有吸引力的新语法。
设计小组对于这个版本在语言设计方面有几个重要的目标。首先(可能对那些认为代码是一种艺术的人来说最为重要),他们想要确保编程人员在编写C++代码时感到很自然,而且通过对ISO C++标准的纯粹扩展可以提供一种优雅的语法。他们想要让编程人员轻松地使用C++编写可验证的代码来支持部分信任的情况,例如SQL Server 2005中的ClickOnce部署、窗体设计支持和托管代码宿主。他们不想为任何比C++“更低级”的语言提供任何空间。他们想把。NET的全部强大功能带给C++,而与此同时也把C++的强大功能带给。NET.他们在各个方面都取得了骄人的成功。
新的扩展规范叫做C++/CLI,并且现在正在进行标准化的工作。要体验一下新的语言扩展,请参见于2003年9月21日在线公布的候选基本文档。
对任何阅读采用新语法编写的代码的人来说,最容易注意到曾经在托管扩展中以双下划线关键字定义垃圾回收类、属性等的流行做法已经成为过去。虽然这样一些关键字仍然保留着,并且还加入了一些新的关键字,但它们现在已经不经常使用了,并且也不会影响到代码的可读性。这些双下划线的关键字由两种新类型的关键字来代替:上下文敏感的关键字和间隔排列的关键字。上下文敏感的关键字是只有在特定上下文中才使用的关键字,而间隔排列的关键字是在与其他关键字组合时才使用的关键字。例如,托管扩展中的__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:
// standard property syntax
property double Grade
{
double get() { return m_grade; }
void set(double newGrade) { m_grade = newGrade; }
}
// trivial property
// compiler can generate accessors and backing store
property String^ Name;
}
在新的语法中,类型以“形容词类”的形式声明,其中,形容词描述您正在创建的类是什么类型,如下所示:
class N { /*…*/ }; // native type
ref class R { /*…*/ }; // CLR reference type
value class V { /*…*/ }; // CLR value type
interface class I { /*…*/ }; // CLR interface type
enum class E { /*…*/ }; // CLR enumeration type
在之前的语言版本中,类型被声明时就可以确定它的使用范围及方式。只有本机类或结构和托管值类型可以在堆栈上创建。托管引用类总是存在于托管堆当中。在Visual C++ 2005中,所有的类型,无论是本机的还是托管的,都在堆栈上创建,它使用基于堆栈的确定性清理语义来完成这一功能。
要在本机堆上实例化类型T的一个对象,可以使用“new T”。这样就可以返回一个指向本机堆上的对象地址的指针(一个在Visual Studio .NET 2002和Visual Studio .NET 2003中称为__nogc指针概念)。为了在托管堆上实例化类型T的一个对象,Visual C++ 2005引入了gcnew这一关键字,它与new关键字的使用方式相同。调用“gcnew T”可以返回指向托管堆中整个对象的一个句柄。句柄(handler)是在Visual C++ 2005中引入的一个新构造,它类似于托管扩展中的__gc指针。要在堆栈上实例化T类型的对象,标准的“T t;”声明就已经足够了。
为了公平起见,还是介绍一下是如何定义实例化的。托管引用类总是存在于托管堆当中,而本机类型总是存在于堆栈或本机堆当中。当一个托管引用被声明为存在于堆栈上时,编译器实际上还会在托管堆上对其进行实例化。
这样会带来一些问题。当在堆栈上的实例超出它的使用范围时会怎样?这个实例将如何被清理掉?许多C#开发人员一直在抱怨C#语言缺少确定性清理。C#语言提供using关键字来简化IDisposable对象的处置,但这需要额外的代码,而且与C++开发人员所熟悉的析构函数的模式相比显得尤为笨拙。在C#中,安全的清理工作在默认情况下是无法进行的,它需要进行显式的编码。例如,请考虑图3中的第一个C#代码片断。StreamReader对象是在托管堆上声明的。当这个方法执行完毕之后,StreamReader的实例就没有任何引用存在了。然而,直到垃圾回收器运行时,这个对象才会被清理掉。直到那时,所用的文件才会被关闭,而在此之前,应用程序会一直占用其打开的文件句柄。要添加确定性清除,必须使用由利用非托管资源的类实现的IDisposable接口。
图3中的第二个代码示例显示了C#中的新代码的外观。其实这种方法也未尝不可,而且也还算有一定的可读性。但当开始加入更多需要清理的对象时,您的代码就会变得越来越难懂。而且,任何您忘记清理的对象都会在最后垃圾回收器实际运行时为finalizer线程增加负担。而与此同时,也许已经锁定了一些有价值的资源。这一点在查看Visual Basic .NET中的同等实现时显得尤为不堪,同样如图3所示(尽管Visual Basic 2005增加了与C#相类似的using语句)。
图 3 确定性清理
实现 代码
没有确定性清理的 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();
}