本文将分成三部分,他们分别是引言、Destructor,Finalizer的语法表示、如何保证Destructor,Finalizer与其他语言兼容。
  一、 引言
   资源是一个很大的范畴,先让我确定一下我们这里谈论的资源包括哪些内容。这里专指在面向对象编程中一个对象实例所使用的资源,他包括对象本身所占有的内存(对象占有内存的大小由对象字段成员来决定,字段成员越多占有的内存就越大)以及其字段成员(Field member)所使用的资源,如文件句柄,数据库链接等等。相信大家比我到清楚在一个对象不再被使用时应该释放其占有的资源,在清理对象所占有的内存之前,执行一个特定的函数,释放字段成员所使用的资源。比如一个文件对象,我们在delete之前得调用Close函数。C++/CLI中的析构器(Destructor)、终结器(Finanlizer)便扮演这个特定函数的角色。在探讨这两个函数之前我们先回忆一下与C++/CLI有着一定关系的ISO C++与.NET平台(ISO C++是他的前辈,并且C++/CLI对ISO C++是兼容的,他们的兼容性已超出本文范围,我们可以在往后再一起讨论;.NET平台是C++/CLI的运行平台),看看他们俩是如何完成资源清理的,这样能够帮助我们更好地理解C++/CLI中的资源清理。
  
   ISO C++面对的是无虚拟机环境,直接根操作系统或是硬件打交道,资源的回收必须由程序员完成,即在某个对象不再使用时得手动地进行资源释放。如果是栈对象则在超出作用域时会自动调用析构器,同时释放对象自己所占有的内存;若是堆对象,只有程序员使用delete pointer 时才会调用pointer所指向对象的析构器,接着释放pointer所指向对象的内存。
  
   .NET平台的一个主要特点是,托管内存,内存的回收交给垃圾回收器(GC)来管理。它会检测到哪些对象不再被使用,便回收其所占用的内存。如果该对象所属的类型实现了Finalize()函数,则会在回收内存之前调用该函数。Finalize函数的作用与ISO C++中的析构器作用类似,在对象被销毁之前释放其字段成员使用的其他资源。
  
   比较一下ISO C++与.NET平台的资源清理,我们不难发现,ISO C++的资源清理是手动的、确定的(调用时机我们可以控制);.NET平台下的资源释放是自动的、不确定的(调用时机我们不能控制)。他们的各自缺点是, ISO C++程序员关注哪些对象不再被使用,调用delete pointer,否则会造成资源泄漏;.NET平台下的一些稀缺资源不能得及时的释放。
  
   在C++/CLI中,析构器与终结器同时出现,解决了了ISO C++与.NET平台的不足,当然同时也没有丢失他们的优点。如果对象所使用的资源是稀缺的,必须确定性的释放,我们便可调用析构器,要是万一我们遗忘了调用析构器,最后垃圾回收器(GC)会调用帮我们调用终结器。到此我们现在可以断言,析构器与终结器所做的事情是一样的,释放其字段成员所使用的资源,只是调用者不一样,一个是程序员一个是垃圾回收器(GC)。最后再补充一下,这里所说的释放其字段成员所使用的资源,并不包括字段本身使用的内存。比如说一个对象中包含有一个文件句柄字段,他会占用4 个Byte的内存,这块内存资源在析构器或终结器中都不会被回收,他的回收将发生在最后,垃圾回收器回收托管内存时。
  
   我们了解了析构器与终结器存在的意义自后,接下来我了解一下他们的语法表示。
  
  
  二、 Destructor,Finalizer的语法表示
  ~ClassName(){…..} //析构器,”~”加类名称
  
  !ClassName(){…...} //终结器,”!”加类名称
  
  假如我们定义一个MyClass类型,他们语法表示如下:
  
  
  
  public ref class MyClass
  
  {
   public:
   ~MyClass() //析构器
   {…}
   !MyClass() //终结器
   {…}
  };
  
  
  
   从第一部分的内容我们知道析构器与终结器是一样的,为了避免代码的重复我们可以在析构器中调用终结器(MSDN推荐这样调用,见Destructors and Finalizers in Visual C++,目前还没弄明白为什么是析构器调用终结器,从函数调用上来看他们两谁调用谁都是一样的),于是我们的代码可以表现如下。
  
  public ref class MyClass
  {
   public:
   ~MyClass() //析构器
   {
   this->!MyClass(); //在析构器中调用析构器
   }
   !MyClass() //终结器
   {
   //TODO,释放字段成员所使用的资源
   }
  };
  
  
  
  
  
  
  MyClass ^myClass = gcnew MyClass();
  
  delete myClass; //调用析构器
  
  myClass = nullptr; //让 myClass 指向空对象, 这样便于垃圾回收器回收myClass原先所向对象所占有的内存。在此再啰唆一下delete关键字,C++/CLI保留了ISO C++ delete关键字,但是他语意有了一些变化。在ISO C++中,他会先调用指针所指向对象的析构器,然后释放对象所占用的内存。在C++/CLI中只会调用析构器,对象所占有的内存不会马上被回收,最终交给垃圾回收器来回收。这源于内存模型的变化,C++/CLI中的引用类型只能分配在托管内存中,托管内存是不能由程序员来显示回收的(当然GC.Collection()函数可以显示让垃圾回收器进行内存回收,当并不建议这样做,除非一些特殊情况)。
  
   大家都知道.NET平台支持多种语言,为了保证不同语言编写的的类型之间能够很好地相互访问,微软定义了一个通用语言规范(Common Language Specification,简称CLS),满足CLS的类型及类型成员便可在不同语言间无缝交互。那么接下来就让我们探讨一下Destructor,Finalizer是如何做到与其他语言兼容的。
  
  
  三、如何保证Destructor,Finalizer与其他语言兼容
  这里我们就从C#的角度出发,分析其是如何实现Destructor,Finalizer与C#的兼容,即如何在C#中访问Destructor,Finalizer并保持语意清晰,如果我们直接使用函数名来访问(~ClassName(),!ClassName()),那看起来就不那么完美了。
  
   查看C++/CLI Language Specification,得知C++/CLI不允许程序员显示的实现(implement)IDisposable接口,当我们给一个类型写析构器时,编译器会自动将让类型实现IDisposable接口。析构器的确定资源释放与IDisposable接口的语意一样(MSDN, IDisposable 定义一种释放分配的非托管资源的方法),在C#中调用Dispose()函数便是我们实现的析构器。终结器从名称上我们很容易想到Finalize()函数,只要编译器给我们生成Finalize()函数,并在该函数中调用!ClassName()函数即可。编译器如何处理这一问题,请看以下的代码与注释。
   MyClass类型的C++/CLI代码如下:
  
  
  
  using namespace System;
  using namespace System::IO;
  public ref class MyClass
  {
  public:
   MyClass() //构造函数,初始化字段成员
   {
   m_stream = gcnew FileStream("C:\\test.txt",FileMode::Create,FileAccess::Write);
   m_writer = gcnew StreamWriter(m_stream);
   m_writer->Write("test");
   }
   ~MyClass() //析构器
   {
   this->!MyClass(); //调用终结器
   }
   !MyClass() //终结器
   {
   delete m_writer; //释放字段成员所占有的资源
   delete m_stream; //释放字段成员所占有的资源
   }
  private:
   FileStream ^m_stream; //字段
   StreamWriter ^m_writer; //字段
  }
  
   以下在为通过Reflector查看的编译结果:
  
  
  
  public class MyClass : IDisposable /**//*C++/CLI源码中并没有显示实现该接口,是编译器给我们加上的*/
  {
   // Fields
   private FileStream m_stream;
   private StreamWriter m_writer;
  
  // Methods
  /**//*注意一下三个方法,从名称上,我们不难发现他们就是我们在MyClass实现时所写的三个方法。只有构造保持没变。析构器与终结器前面都加上了private访问修饰符,因此在类型之外是无法访问到析构器和终结器的。*/
   private void !MyClass();
   public MyClass();
   private void ~MyClass();
  
  /**//*以下三个函数是编译器给我们加上的,他们是实现与CLS兼容的关键,请注意他们的逻辑关系,逻辑关系也比较简单,看一下代码就清楚了。建议阅读时我们不妨试着在脑海里执行一下,Dispose(),Finalizer()方法。*/
  
  // Dispose()为IDisposable成员。
  public sealed override void Dispose()
  {
   this.Dispose(true);
   //传true调用Dispose(bool) 函数
   GC.SuppressFinalize(this);
   /**//*如果用户调用了Dispose(),通知垃圾回收器不再执行Finalize函数*/
  }
  
   /**//*这就是我们先前说的,由垃圾回收器调用的函数*/
  protected override void Finalize()
  {
   this.Dispose(false);
   //传false调用Dispose(bool) 函数
  }
  
  /**//*关键是下面这个函数Dispose(bool),他确保了我们在别的语言或垃圾回收器可以调到正确的函数,C++/CLI中析构器,终结器*/
  protected virtual void Dispose([MarshalAs(UnmanagedType.U1)] bool flag1)
  {
   if (flag1)
   {
   this.~MyClass();
   /**//*Dispose() 调用该函数传入true,便执行该语句块,实现了C++/CLI与CLS兼容,调用Dispose(),便是C++/CLI中的析构器,确定性资源释放*/
   }
   else
   {
   try
   {
   this.!MyClass();
   /**//*Finalize() 调用该函数传入false,便执行该语句块,实现了C++/CLI与CLS兼容,调用Finalize(),便是C++/CLI中的终结器,确保对象在被销毁前释放其占有的其他资源*/
   }
   finally
   {
   base.Finalize();
   }
   }
  }
  }

C++/CLI中的资源清理(Destructor,Finalizer)相关推荐

  1. 【C#本质论 十一】合式类型(二)程序集引用、XML注释、垃圾回收和资源清理

    上一节介绍到了如何进行Object方法重写和操作符重载,本篇博客来接着介绍合式类型剩余的内容: 程序集引用及命名空间定义 其中一些相对简单的内容就不进行过多的介绍了,例如引用其他程序集,只需要注意三种 ...

  2. TPP多租户隔离之资源清理

      双11的时候TPP引入了ajdk多租户,对场景的cpu进行隔离,参考文章 <TPP稳定性之场景隔离和多租户>.文章中对tpp提供给算法方案的二方服务客户端进行改造,这些共享的二方服务注 ...

  3. Linux线程退出、资源回收、资源清理的方法

    首先说明线程中要回收哪些资源,理解清楚了这点之后在思考资源回收的问题. 1.子线程创建时从父线程copy出来的栈内存; 线程退出有多种方式,如return,pthread_exit,pthread_c ...

  4. C#笔记09 结构、枚举、异常、泛型、操作符重载、dll、垃圾回收与资源清理、XML注释

    文章目录 结构体struct 枚举enum 异常Exception 执行try最近的最贴切的catch 继承Exception以定义异常 泛型 泛型的约束where 操作符重载 类型转换操作符重载 d ...

  5. 使用 Kube-capacity CLI 查看 Kubernetes 资源请求、限制和利用率

    使用 Kube-capacity CLI 查看 Kubernetes 资源请求.限制和利用率 Kube-capacity  是一个简单而强大的  CLI ,它提供了 Kubernetes 集群中资源请 ...

  6. android 集合 内存泄漏,Android内存泄漏第二课--------(集合中对象没清理造成的内存泄漏 )...

    一.我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大.如果这个集合是static的话,那情况就更严重 ...

  7. 从jar包中读取资源文件

    :[解惑]深入jar包:从jar包中读取资源文件 精华帖 (3) :: 良好帖 (15) :: 新手帖 (9) :: 隐藏帖 (0) 作者 正文 Heart.X.Raid 等级: 性别: 文章: 72 ...

  8. Android中的资源访问

    Android中的资源是指非代码部分,指外部文件. assets中保存的一般是原生的文件,例如MP3文件,Android程序不能直接访问,必须通过AssetManager类以二进制流的形式来读取. r ...

  9. android 6.0 自定义application,Android6.0之App中的资源管理对象创建

    Android与资源管理相关的类Resouces和AssetManager很有必要清楚他们的创建过程. 与资源查找与加载操作相关的类 资源查找与加载主要是靠Android资源管理框架来完成的,而And ...

最新文章

  1. Git workflow
  2. 谷歌Deep Bootstrap Framework:在线优化角度理解神经网络
  3. Nginx特性验证-反向代理/负载均衡/页面缓存/URL重定向
  4. 基础知识—函数-函数概述
  5. 【付费毕设】php mysql社团报名管理系统
  6. inline在C99以及Gcc中的处理方式[转]---很好的一篇总结
  7. 如何在 Mac 上播放 Keynote 演示文稿?
  8. C# OpenCV OpenCVSharp应用实例--LCD屏幕脏污检测
  9. css3 眼珠旋转动画,CSS3小猫咪眼睛随鼠标移动动画特效
  10. 本台计算机没有权限使用网络资源,你可能没有权限使用网络资源,详细教您你可能没有权限使用网络资源怎么解决...
  11. java 判断是否夏令时_Java日期夏令时的问题
  12. python分析每月销售数据_如何用Python分析销售数据
  13. 通过网址自动网页截图(Selenium实现)
  14. Java并发编程模拟管程(霍尔Hoare管程、汉森Hansan管程、MESA管程)
  15. 短视频剪辑技巧分享,先做排序不能忘,观看效果会更佳
  16. 产品经理必须会的软件——office
  17. 数学:(一直很浮躁)
  18. 长度单位换算python_长度单位换算表-在线长度单位转换器
  19. LaTeX 第一页不显示页码
  20. 全国电子商务人才专业化从业认证考试初级教程(公共基础教程)(全国电子商务人才丛业能力教育指定教材)

热门文章

  1. 如何用笔记本建立wifi热点
  2. golang之‘...‘的用法
  3. Java按照时间顺序从hbase中读出数据
  4. mybatis的动态sql的一些记录
  5. 最长公共前缀(java实现)
  6. 【Redis】Linux下Redis安装与redis-desktop-manager使用(无法连接Redis服务器解决方法)...
  7. Django中的模型继承
  8. 1.记住密码 提示框
  9. mysql主从复制--转载
  10. python--paramiko模块