7

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

我已经阅读或浏览了这两篇文章()并查看了标准并查看了其他一些问题,尤其是这篇文章。

我有各种信息:

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

但也有很多混乱,部分原因是

  1. 终结器在 C# 中称为析构函数
  2. 析构函数在内部生成 Dispose 和 Finalize 方法(不确定 Finalize),但 Finalize 方法不是终结器
  3. 析构函数的语义在 C++ 中是不同的,并且通常具有确定性清理和垃圾收集的复杂性

我想要的答案是一个类的示例,该类具有它可能包含的所有不同类型的数据(托管、非托管、托管但一次性,无论您能想到什么)以及正确编写的析构函数和终结器。

我有两个更具体的问题:

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

1 回答 1

5

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

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

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

所以你需要实现一个专门的清理代码

有2种可能:

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

  • 您不知道何时不再需要这些资源,因此您将清理推迟到最后一刻,当包装资源的对象被 GC 收集时,这是通过终结器实现的

您猜在第一种情况下要好得多,因为您不会消耗比您需要的更多的内存,并且您避免了 GC 过程的一些额外开销。

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

CLR 级别,没有确定性清理之类的东西,只有终结器。

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

  • 在本机 C++ 中,您在退出范围或“删除”时调用了析构函数

  • 在 .Net 世界中,您拥有 Dispose 模式

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

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

关于您的第一系列观点的一些澄清:

  1. 是的

  2. 析构函数也负责清理非托管资源;如果您在其中分解了清理代码,它可以调用终结器。

  3. 他们在技术上可以但在逻辑上你应该用一个简单的布尔值守卫来防止它

  4. 是的,因为应该完成所有清理工作,所以您要求 CLR 不要完成对象

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

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

  7. 你应该确保是这样

和其他人:

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

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

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

于 2013-11-03T16:17:47.003 回答