【问题标题】:How do I write proper destructors and finalizers?如何编写正确的析构函数和终结器?
【发布时间】:2013-11-07 05:06:26
【问题描述】:

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

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

我有各种信息:

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

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

  1. 终结器在 C# 中称为析构函数
  2. 析构函数在内部生成 Dispose 和 Finalize 方法(不确定 Finalize),但 Finalize 方法不是终结器
  3. C++ 中析构函数的语义不同 以及总体上同时进行确定性清理和垃圾收集的复杂性

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

我有两个更具体的问题:

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

【问题讨论】:

  • 如果给出不同类型数据的示例,并向我们展示您的尝试,您将获得更好的响应,然后我们可以提供反馈。跨度>

标签: c++-cli destructor finalizer


【解决方案1】:

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

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

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

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

有两种可能:

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

  • 您不知道何时不再需要这些资源,因此您在可能的最后一刻推迟清理,当包装资源的对象被 GC 收集时,这是通过finalizers

  • 实现的

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

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

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

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

  • 在原生 C++ 中,您在退出作用域或“删除”时调用了析构函数

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

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

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

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

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

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

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

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

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

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

还有其他人:

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

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

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

【讨论】:

  • 请注意,~MyClass 被映射到 Dispose 而 !MyClass 被映射到 Finalize 方法! -Microsoft Doc。 Dispose 方法由编译器自动生成并调用 GC::SuppressFinalize 因此 !MyClass 应在 ~MyClass 方法中显式调用,如果您需要调用 finalized!
猜你喜欢
  • 2016-11-04
  • 2012-05-03
  • 2010-12-24
  • 1970-01-01
  • 1970-01-01
  • 2015-08-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多