上一篇:C++:最强大的.NET语言之装箱 >>
C++:最强大的.NET语言之内存与资源
内存管理
本地C++为程序员提供了超越内存管理的直接控制能力,在堆栈上分配一个对象,意味着只有在进入特定函数时,才会为对象分配内存,而当函数返回或堆栈展开时,内存被释放。可使用操作符new来动态地为对象分配内存,此时内存分配在CRT堆中,并且需要程序员显存地对对象指针使用操作符delete,才能释放它。这种对内存的精确控制,也是C++可用于编写极度高效的程序的原因之一,但如果程序员不小心,这也是内存泄漏的原因。另一方面,你不需要求助于垃圾回收器来避免内存泄漏--实际上这是CLR所采取的方法,而且是一个非常有效的方法,当然,对于垃圾回收堆,也有其他一些好处,如改进的分配效率及引用位置相关的优势。所有这一切,都可以在C++中通过库支持来实现,但除此之处,CLR还提供了一个单一的内存管理编程模型,其对所有的编程语言都是通用的,想一想与C++中COM自动化对象相交互和调度数据类型所需做的一切工作,就会发现其重要意义所在--横跨数种编程语言的垃圾回收器,作用是非常巨大的。
为了效率,CLR也保留了堆栈的概念,以便值类型可在其上分配,但CLR也提供了一个newobj中间语言指令,以在托管堆中分配一个对象,但此指令只在C#中对引用对象使用操作符new时提供。在CLR中,没有与C++中的delete操作符对应的函数,当应用程序不再引用某对象时,分配的内存最后将由垃圾回收器回收。
当操作符new应用于引用类型时,托管C++也会生成newobj指令,当然,对此使用delete操作符是不合法的。这确实是一个矛盾,但同时也证明了为什么用C++指针概念来表示一个引用类型不是一个好的做法。
在内存管理方面,除了上述在对象构造一节讨论过的内容,C++/CLI没有提供任何新的东西;资源管理,才是C++/CLI的拿手好戏。
资源管理
CLR只有在资源管理方面,才能胜过本地C++。Bjarne Stroustrup的"资源获取即初始化"的技术观点,基本定义了资源类型的模式,即类的构造函数获取资源,析构函数释放资源。这些类型是被当作堆栈上的局部对象,或复杂类型中的成员,其析构函数自动释放先前分配的资源。一如Stroustrup所言"对垃圾回收机制来说,C++是最好的语言,主要是因为它生成很少的垃圾。"
也许有一点令人惊讶,CLR并没有对资源管理提供任何显式运行时支持,CLR不支持类似析构函数的C++概念,而是在 .NET Framework中,把资源管理这种模式,提升到一个IDisposable核心接口类型的中心位置。这种想法源自包装资源的类型,理应实现此接口的单一Dispose方法,以便调用者在不再使用资源时,可调用该方法。不必说,C++程序员会认为这是时代的倒退,因为他们习惯于编写那些缺省状态下清理就是正确的代码。
因为必须要调用一个方法来释放资源,由此带来的问题是,现在更难编写"全无异常"的代码了。因为异常随时都可能发生,你不可能只是简单地在一段代码后,放置一个对对象的Dispose方法的调用,这样做的话,就必须要冒资源泄漏的风险。在C#中解决这个问题的办法是,使用try-finally块和using语句,在面对异常时,可提供一个更可靠的办法来调用Dispose方法。有时,构造函数也会使用这种方法,但一般的情况是,你必须要记住手工编写它们,如果忘记了,生成的代码可能会存在一个悄无声息的错误。对缺乏真正析构函数的语言来说,是否需要try-finally块和using语句,还有待论证。
| using (SqlConnection connection = new SqlConnection("Database=master; Integrated Security=sspi")) { SqlCommand command = connection.CreateCommand(); command.CommandText = "sp_databases"; command.CommandType = CommandType.StoredProcedure; connection.Open(); using (SqlDataReader reader = command.ExecuteReader()) { while (reader.Read()) { Console.WriteLine(reader.GetString(0)); } } } |
对托管C++来说,情节也非常类似,也需要使用一个try-finally语句,但其是Microsoft对C++的扩展。虽然很容易编写一个简单的Using模板类来包装GCHandle,并在模板类的析构函数中调用托管对象的Dispose方法,但托管C++中依然没有C# using语句的对等物。
| Using<SqlConnection> connection(new SqlConnection(S"Database=master; Integrated Security=sspi")); SqlCommand* command = connection->CreateCommand(); command->set_CommandText(S"sp_databases"); command->set_CommandType(CommandType::StoredProcedure); connection->Open(); Using<SqlDataReader> reader(command->ExecuteReader()); while (reader->Read()) { Console::WriteLine(reader->GetString(0)); } |
想一下C++中对资源管理的传统支持,其对C++/CLI也是适用的,但C++/CLI的语言设计犹如为C++资源管理带来了一阵轻风。首先,在编写一个管理资源的类时,对大部分CLR平台语言来说,其中一个问题是怎样正确地实现Dispose模式,它可不像本地C++中经典的析构函数那样容易实现。当编写Dispose方法时,需要确定调用的是基类的Dispose方法--若有的话,另外,如果选择通过调用Dispose方法来实现类的Finalize方法,还必须关注并发访问,因为Finalize方法很可能被不同的线程所调用。此外,与正常程序代码相反,如果Dispose方法实际上是被Finalize方法调用的,还需要小心仔细地释放托管资源。
C++/CLI并没有与上述情况脱离得太远,但它提供了许多帮助,在我们来看它提供了什么之前,先来快速回顾一下如今的C#和托管C++有多么接近。下例假设Base从IDisposable派生。
| class Derived : Base { public override void Dispose() { try { //释放托管与非托管资源 } finally { base.Dispose(); } } ~Derived() //实现或重载Object.Finalize方法 { //只释放非托管资源 } } |
托管C++也与此类似,看起来像析构函数的代码其实是一个Finalize方法,编译器实际上插入了一个try-finally块并调用基类的Finalize方法,因此,C#与托管C++相对容易编写一个Finalize方法,但在编写Dispose方法时,却没有提供任何帮助。程序员们经常使用Dispose方法,把它当作一个伪析构函数以便在代码块末执行一点其他的代码,而不是为了释放任何资源。
C++/CLI认识到了Dispose方法的重要性,并在引用类型中,使之成为一个逻辑"析构函数"。
| ref class Derived : Base { ~Derived() //实现或重载IDisposable::Dispose方法 { //释放托管与非托管资源 } !Derived() //实现或重载IDisposable::Dispose方法 { //只释放非托管资源 } }; |
对C++程序员来说,这让人感觉更自然了,能像以往那样,在析构函数中释放资源了。编译器会生成必要的IL(中间语言)来正确实现IDisposable::Dispose方法,包括抑制垃圾回收器调用对象的任何Finalize方法。事实上,在C++/CLI中,显式地实现Dispose方法是不合法的,而从IDisposable继承只会导致一个编译错误。当然,一旦类型通过编译,所有使用该类型的CLI语言,将只会看到Dispose模式以其每种语言最自然的方式得以实现。在C#中,可以直接调用Dispose方法,或使用一个using语句--如果类型定义在C#中。那么C++呢?难道要对堆中的对象正常地调用析构函数?此处当然是使用delete操作符了,对一个句柄使用delete操作符将会调用此对象的Dispose方法,而回收对象的内存是垃圾回收器该做的事,我们不需要关心释放那部分内存,只要释放对象的资源就行了。
| Derived^ d = gcnew Derived(); d->SomeMethod() delete d; |
如果表达式中传递给delete操作符的是一个句柄,将会调用对象的Dispose方法,如果此时再没有其他对象链接到引用类型,垃圾回收器就会释放对象所占用的内存。如果表达式中是一个本地C++对象,在释放内存之前,还会调用对象的析构函数。
毫无疑问,在对象生命期管理上,我们越来越接近自然的C++语法,但要时刻记住使用delete操作符,却不是件易事。C++/CLI允许对引用类型使用堆栈语义,这意味着你能用在堆栈上分配对象的语法来使用一个引用类型,编译器会提供给你所期望的C++语义,而在底层,实际上仍是在托管堆中分配对象,以满足CLR的需要。
| Derived d; d.SomeMethod(); |
当d超出范围时,它的Dispose将会被调用,以释放它所占用的资源。再则,因为对象实际是在托管堆中分配的,所以垃圾回收器会在它的生命期结束时释放它。来看一个ADO.NET的例子,它与C++/CLI中的概念非常相似。
| SqlConnection connection("Database=master; Integrated Security=sspi"); SqlCommand^ command = connection.CreateCommand(); command->CommandText = "sp_databases"; command->CommandType = CommandType::StoredProcedure; connection.Open(); SqlDataReader reader(command->ExecuteReader()); while (reader.Read()) { Console::WriteLine(reader.GetString(0)); } |
下一篇:C++:最强大的.NET语言之对象构造 >>
相关文章:
- · C++/CLI思辨录之代理构造函数
- · C++/CLI思辨录之拷贝构造函数
- · C++/CLI思辨录之Object的对象布局
- · VC++.NET中使用GDI+创建特效字体
- · C++/CLI中栈对象的设计问题
- · it培训暑期掀热潮 软件行业短缺四类人才
- · VB.NET开发人员必备参考10本书目
- · 在VS2005中打造自己的Starter Kits
- · Visual Basic.NET实现主/从数据表视图
- · C#中ref和out的使用小结
- · Visual C#命名空间详解
- · C#中加强ListView控件的功能
- · 用托管C++编写自定义Web组合控件
- · 叩开C#之门系列之C#与面向对象编程语言
- · C# 2.0 套接字编程实例初探
- · 叩开C#之门系列之几个重要名词
- · 炮打“华军司令部”,“迅雷”的一张大字报
- · C#+低级Windows API钩子拦截键盘输入
- · 在C#中动态调用native dll的导出函数
- · C#3.0新特性之隐式类型局部变量的使用
- · 试用报告:笔记本中体验win vista的魅力
- · 用C#实现HTTP协议下的多线程文件传输
- · windows vista 5466细节全览(多图)
- · 性能更佳 微软发布vista最新测试版5466
- · 如何在C#中播放AVI短片并使背景透明
- · 主流测试:vista你为游戏准备好了吗?
- · windows xp和vista双系统安装不完全攻略
- · 用C#开发Windows服务监控系统使用
- · C#中父窗口和子窗口之间实现控件互操作
- · Visual C# 2.0泛型编程基础
- · Visual C#泛型学习之泛型介绍
- · 为非主流主机优化 打造最强vista 5456
- · C#与松下FP∑可编程控制器的通信
- · 谈谈购买win vista的十大理由及放弃原因
- · Visual C#使用DirectX实现视频播放
- · Visual C#程序员面试基础问题和答案
- · 用户需注意!选购“vista笔记本”要谨慎
- · intel透露最新windows vista认证要求
