如何编写适当的析构函数和终结符?

时间:2013-10-28 12:16:04

标签: c++-cli destructor finalizer

我试图弄清楚如何在C ++ / CLI中正确清理我的对象。

我已阅读或浏览过这两篇文章(onetwo),并查看了the standard并查看了其他一些问题this one

我有各种各样的信息:

  1. 终结器应该清理非托管资源(因此当对象被垃圾收集时,所有内容都会被清除。
  2. 析构函数应该清理托管资源(删除Foo或Foo.Dispose()?)并调用终结器(根据1
  3. 可以多次调用析构函数和终结器(参见3第26页,结束于8.8.8)
  4. 如果调用析构函数,则不再调用终结器(根据1)(不是通过CLR,也就是说,您仍可以自己调用它)
  5. 析构函数将调用基类析构函数(参见3第25页)
  6. 具有终结器的类应始终具有析构函数(可能是为了确定性地清理非托管资源)
  7. 对终结者的调用不会调用基类终结器(3 19.13.2 p.131)
  8. 但是,由于

    这一事实导致了很多混乱
    1. 终结器在C#中被称为析构函数
    2. 析构函数在内部生成Dispose和Finalize方法(不确定Finalize)但Finalize方法不是终结器
    3. 析构函数的语义在C ++中是不同的 以及通常具有确定性清理和垃圾收集的复杂性
    4. 我想要的答案是一个类的例子,它包含它可能包含的所有不同类型的数据(托管,非托管,托管但是一次性的,你能想到的其他任何东西)以及正确编写的析构函数和终结器。

      我还有两个更具体的问题:

      1. 通过只拥有一个bool hasBeenCleanedUp成员处理多次被调用的可能性并使析构函数/终结符中的整个代码以此为条件可以接受吗?
      2. 什么样的数据只能由析构函数清理,但不能在终结器中清理,因为它可能已经被gc清理过了?

1 个答案:

答案 0 :(得分:5)

对您的问题不是一个完整的答案,但是太长,无法发表评论。

完全托管的世界中,每个对象只引用托管对象,不需要终结器或析构函数,因为唯一的资源是内存 GC会照顾它

当您引用非托管资源时,如果您不再需要它,则有责任释放它们

因此,您需要实施专用清理代码

有两种可能性:

  • 你知道当你不再需要非托管资源时,你可以确定性地运行你的清理代码,这是通过析构函数/ Dispose 实现的

  • 你不知道什么时候不再需要这些资源所以你在最后一刻推迟清理,当GC收集包装资源的对象时,这是通过终结者

  • 实现的

您认为在第一种情况下更好,因为您不会消耗超过您需要的内存,并且可以避免GC流程的额外开销。

您通常同时实现这两者,因为实例的生命周期可能因使用情况而异。

CLR级别,没有确定性清理,只有终结器。

语言/ API级别,支持确定性清理:

  • 在本机C ++中,您在退出作用域或“删除”时会调用析构函数

  • <。li>

    在.Net世界中,你有Dispose模式

  • 在纯托管C ++ / CLI中,世界析构函数映射到Dispose

当您有机会确切知道何时可以运行清理代码时,您可以调用(或让基础架构调用)析构函数。清理完成后,您可以摆脱所有的完成过程,以便在下一个GC时立即收集对象。

关于你的第一个要点的一些澄清:

  1. 析构函数也负责清理非托管资源;它可以调用终结器,如果它是你清理代码的因素。

  2. 他们在技术上可以但逻辑上你应该用一个简单的布尔警卫来阻止它

  3. 是的,因为所有的清理都应该完成,所以你要求CLR不要最终确定对象

  4. 是的,因为基类知道它分配了哪些资源

  5. 是的,这是用于确定性清理

  6. 你应该确保是这种情况

  7. 其他人:

    1. 是~MyClass映射到Finalize方法的覆盖

    2. 如上所述,析构函数映射到Dispose,但您应该自己实现终结器:!MyClass

    3. 总结:C ++析构函数和Dispose模式用于确定性清理,C#析构函数,C ++ / CLI终结器用于由GC触发的非确定性清理。