为什么在垃圾收集语言中普遍缺少对象析构函数范式?


27

寻找有关垃圾收集语言设计决策的见解。也许语言专家可以启发我?我来自C ++背景,所以这个领域令我感到困惑。

似乎几乎所有具有OOPy对象支持的现代垃圾收集语言(如Ruby,Javascript / ES6 / ES7,Actionscript,Lua等)都完全省略了析构函数/定型化范例。Python似乎是唯一使用它的class __del__()方法。为什么是这样?在具有自动垃圾收集的语言中,语言是否存在功能/理论上的限制,从而无法在对象上有效实现析构函数/完成方法?

我发现非常缺乏这些语言将内存视为唯一值得管理的资源。套接字,文件句柄,应用程序状态如何?由于无法实现自定义逻辑以清除对象最终确定中的非内存资源和状态,因此我需要使用自定义myObject.destroy()样式调用来填充我的应用程序,将清除逻辑置于“类”之外,破坏尝试的封装,并降级我的应用程序由于人为错误而导致资源泄漏,而不是由gc自动处理。

什么是语言设计决策,这些决策导致这些语言无法对对象处理执行任何自定义逻辑?我必须想象有一个很好的理由。我想更好地理解导致这些语言不支持对象销毁/完成的技术和理论决策。

更新:

也许更好的方式表达我的问题:

为什么一种语言会具有带有类或类结构以及自定义实例化(构造函数)的对象实例的内置概念,却完全省略了销毁/最终确定功能?提供自动垃圾收集的语言似乎是支持对象销毁/完成的主要候选方法,因为他们知道当不再使用某个对象时会100%确定。但是,其中大多数语言都不支持它。

我认为在这种情况下可能永远不会调用析构函数,因为那将是核心内存泄漏,gcs旨在避免这种情况。我可以看到一个可能的论点是,直到将来某个不确定的时间,析构函数/ finalizer才会被调用,但这并没有阻止Java或Python支持该功能。

不支持任何形式的对象完成的核心语言设计原因是什么?


9
也许因为finalize/ destroy是谎言?不能保证它会被执行。而且,即使您不知道何时(自动垃圾收集),以及是否仍有必要的上下文(可能已经被收集)也是如此。因此,以其他方式确保状态的一致性是比较安全的,因此可能需要强制程序员这样做。
拉斐尔

1
我认为这个问题是边缘性的。这是我们想要娱乐的一种编程语言设计问题,还是对于一个更注重编程的站点而言的问题?请社区投票。
拉斐尔

14
在PL设计中,这是一个很好的问题,让我们来解决。
安德烈·鲍尔

3
这实际上不是静态/动态区别。许多静态语言没有终结器。实际上,少数派中没有终结器的语言不是吗?
安德烈·鲍尔

1
认为这里存在一些问题...如果您再定义一些术语会更好。Java有一个与对象破坏无关但与方法退出无关的finally块。还有其他处理资源的方法。例如在Java中,连接池可以处理未使用的[x]时间段的连接并回收它们。不优雅,但有效。您的问题的部分答案是,垃圾回收大致上是不确定的,不是即时的过程,并且不是由不再使用的对象而是由内存限制/触发的上限驱动的。
vzn15年

Answers:


10

您正在谈论的模式(对象知道如何清理其资源)分为三个相关类别。我们不要将析构函数终结器混为一谈,只有一个与垃圾回收有关:

  • 终结器图案:清理方法自动声明,由程序员定义的,自动调用。

    终结器在垃圾回收器释放之前自动调用。如果采用的垃圾收集算法可以确定对象生命周期,则该术语适用。

  • 程序员定义的自动声明的析构函数模式:cleanup方法有时仅自动调用。

    可以为堆栈分配的对象自动调用析构函数(因为对象生存期是确定性的),但是必须在堆分配的对象的所有可能的执行路径上显式调用析构函数(因为对象生存期是不确定的)。

  • 所述处理器图案:清理方法中声明,所定义,并且由程序员调用。

    程序员制作一个处置方法并自行调用-这是您的自定义myObject.destroy()方法所在的地方。如果绝对需要处置,则必须在所有可能的执行路径上调用处置器。

终结器是您要寻找的机器人。

终结器模式(您的问题正在询问的模式)是一种机制,用于将对象与系统资源(套接字,文件描述符等)相关联,以使垃圾回收器相互回收。但是,终结器从根本上讲受使用的垃圾收集算法的支配。

考虑您的以下假设:

提供自动垃圾收集的语言……当不再使用某个对象时,它们会100%确定。

从技术上讲是错误的(谢谢@babou)。垃圾回收从根本上讲是关于内存的,而不是对象。收集算法是否或何时不再使用对象的内存取决于算法,以及(可能)对象之间如何相互引用。让我们讨论两种类型的运行时垃圾收集器。有很多方法可以将这些更改和扩充为基本技术:

  1. 跟踪GC。 这些跟踪内存,而不是对象。除非进行扩展,否则它们不会保留对内存中对象的回引用。除非进行了扩充,否则这些GC不会知道何时可以完成对象,即使它们知道何时无法访问其内存。因此,不能保证终结器调用。

  2. 参考计数GC。这些使用对象来跟踪内存。他们使用有向参考图对对象可及性进行建模。如果对象引用图中有一个循环,则该循环中的所有对象将永远不会调用其终结器(显然,直到程序终止)。同样,不能保证终结器调用。

TLDR

垃圾收集非常困难且种类繁多。在程序终止之前,不能保证完成程序调用。


您是正确的,这不是静态的还是动态的。这是垃圾收集语言的问题。垃圾回收是一个复杂的问题,可能是主要原因,因为要考虑许多边缘情况(例如,如果逻辑finalize()导致要清理的对象再次被引用,会发生什么情况?)。但是,无法保证终结器在程序终止之前被调用不会阻止Java支持它。并不是说您的答案是错误的,可能只是不完整。还是一个很好的职位。谢谢。
dbcb

感谢您的反馈。这是尝试完成我的回答的一种尝试:通过显式省略终结器,一种语言迫使其用户管理自己的资源。对于许多类型的问题,这可能是一个缺点。就我个人而言,我更喜欢Java的选择,因为我有终结器的功能没有什么可以阻止我编写和使用自己的处理器。Java说的是:“嘿,程序员。您不是白痴,所以这里是终结器。请小心。”
kdbanman 2015年

1
更新了我的原始问题,以反映这涉及垃圾收集的语言。接受您的答案。感谢您抽出宝贵的时间回答。
dbcb

乐意效劳。我的澄清说明是否使我的答案更清楚?
kdbanman

2
很好。对我来说,真正的答案是语言选择不执行它,因为感知到的价值并不超过实现功能的问题。这并不是不可能的(如Java和Python所示),但是许多语言选择不做一个折衷方案。
dbcb

5

简而言之

终结处理不是由垃圾收集器处理的简单问题。参考计数GC易于使用,但是该GC系列通常是不完整的,需要通过显式触发销毁某些对象和结构并对其进行终结来补偿内存泄漏。跟踪垃圾收集器更为有效,但是与仅标识未使用的内存相比,它们使标识要完成和销毁的对象变得更加困难,因此需要更复杂的管理,而这会花费时间和空间,并且成本很高。实施。

介绍

我假设您要问的是为什么垃圾收集语言不能在垃圾收集过程中自动处理销毁/完成,如备注所示:

我发现非常缺乏这些语言将内存视为值得管理的唯一资源。套接字,文件句柄,应用程序状态如何?

我不同意kdbanman给出的答案。尽管所陈述的事实大部分是正确的,尽管偏向于引用计数,但我认为它们不能正确解释问题中所抱怨的情况。

我认为该答案中发展出的术语不是一个大问题,它更容易使事情混淆。确实,如前所述,术语主要是由激活程序的方式决定的,而不是由它们的操作决定的。关键是,在所有情况下,都需要终结一些清理过程不再需要的对象,并释放它一直在使用的任何资源,而内存只是其中之一。理想情况下,当不再使用该对象时,应该通过垃圾收集器自动完成所有操作。实际上,GC可能会丢失或存在缺陷,这可以通过完成和回收程序的显式触发来补偿。

程序显式触发问题是一个问题,因为当仍在使用中的对象被明确终止时,它可能导致难以分析编程错误。

因此,最好是依靠自动垃圾回收来回收资源。但是有两个问题:

  • 一些垃圾回收技术将允许内存泄漏,从而阻止资源的完全回收。对于参考计数GC,这是众所周知的,但是如果不加注意地使用某些数据组织,则可能在其他GC技术中出现(此处未讨论)。

  • 尽管GC技术可能擅长识别不再使用的内存资源,但是终结其中包含的对象可能并不简单,这使回收这些对象使用的其他资源的问题变得复杂化,这通常是终结的目的。

最后,经常被遗忘的重要一点是,如果提供了适当的挂钩并且认为GC周期的成本值得,那么GC周期可以由任何原因触发,而不仅仅是内存不足。因此,当缺少任何一种资源以释放一些资源时,完全可以启动GC。

引用计数垃圾收集器

引用计数是一种较弱的垃圾收集技术,无法正确处理循环。确实,在销毁过时的结构和回收其他资源方面确实很弱,仅因为它在回收内存方面很弱。但是,使用引用计数垃圾收集器(GC)可以最轻松地使用终结器,这是因为ref-count GC会在其ref计数降至0时回收结构,此时它的地址及其类型都可以静态地获知或动态地 因此,有可能在应用适当的终结器之后精确地回收内存,并在所有指向的对象上递归调用该过程(可能通过终结过程)。

简而言之,使用Ref Counting GC可以轻松实现终结处理,但是确实由于圆形结构而导致该GC的“不完整性”,其遭受的程度与内存回收的程度完全相同。换句话说,使用引用计数,内存的管理与套接字,文件句柄等其他资源一样差

确实,Ref Count GC无法回收循环结构(通常)可能被视为内存泄漏。您不能期望所有GC都能避免内存泄漏。它取决于GC算法以及动态可用的类型结构信息(例如,在 保守的GC中)。

追踪垃圾收集器

没有此类泄漏的更强大的GC系列是跟踪系列,该系列从良好识别的根指针开始探索内存的活动部分。在此跟踪过程中未访问的存储器的所有部分(实际上可以以各种方式分解,但我必须简化)是可以回收的存储器的未使用部分1。这些收集器将回收该程序无法再访问的所有内存部分,无论它做什么。它确实回收了圆形结构,并且更高级的GC基于这种范例的某些变体,有时是非常复杂的。在某些情况下,它可以与引用计数结合使用,并弥补其缺点。

一个问题是您的陈述(在问题末尾):

提供自动垃圾收集的语言似乎是支持对象销毁/完成的主要候选方法,因为他们知道当不再使用某个对象时会100%确定。

在技​​术上不适合跟踪收集器。

100%可以确定的是,不再使用内存的哪些部分。(更确切地说,应该说它们不再可访问,因为如果程序中仍然有指向它们的无用指针,则仍会考虑使用不再根据程序逻辑使用的某些部分。但是,需要进一步的处理和适当的结构才能知道哪些未使用的对象可能已存储在内存的这些现在未使用的部分中。由于该程序不再连接到存储器的这些部分,因此无法从程序的已知信息中确定。

因此,在经过垃圾收集之后,您将剩下一些内存片段,其中包含不再使用的对象,但是先验地没有办法知道这些对象是什么,以便应用正确的终结方法。此外,如果跟踪收集器是标记清除类型,则可能是某些片段可能包含在上一个GC通道中已经完成但由于碎片原因而未使用的对象。但是,可以使用扩展的显式键入来解决。

尽管一个简单的收集器将回收这些内存片段,而事不宜迟,但终结处理需要经过特定的遍历才能探索该未使用的内存,识别其中包含的对象并应用终结处理过程。但是,这种探索需要确定存储在此处的对象的类型,并且还需要确定类型以应用适当的终结处理(如果有)。

因此,这意味着需要花费更多的GC时间(额外的遍历),并可能需要额外的内存开销,以通过各种技术在该遍历中提供正确的类型信息。这些成本可能是巨大的,因为人们通常只想敲定几个对象,而时间和空间开销可能涉及所有对象。

还有一点是,时间和空间开销可能与程序代码的执行有关,而不仅仅是GC的执行。

针对具体问题,我无法给出更准确的答案,因为我不知道您列出的许多语言的具体情况。对于C语言,类型化是一个非常困难的问题,导致了保守的收集器的发展。我的猜测是这也会影响C ++,但我不是C ++专家。汉斯·博姆(Hans Boehm)对保守气相色谱进行了大量研究,似乎证实了这一点。保守GC不能精确地系统回收所有未使用的内存,因为它可能缺少数据的精确类型信息。由于相同的原因,它将无法系统地应用定型程序。

因此,您可以按照某些语言的要求去做。但是它不是免费的。根据语言及其实现的不同,即使您不使用该功能,也可能需要付费。可以考虑使用各种技术和折衷方法来解决这些问题,但这超出了合理大小的答案的范围。

1-这是跟踪收集的抽象表示(包括复制和标记扫掠GC),根据跟踪收集器的类型而有所不同,并且根据复制或标记以及使用扫描。


您提供了大量有关垃圾回收的详细信息。但是,您的答案实际上并不与我的意见不同-您的摘要和我的TLDR本质上是在说同样的话。对于它的价值,我的答案以引用计数GC为例,而不是“强偏差”。
kdbanman 2015年

在更彻底地阅读之后,我看到了分歧。我将进行相应的编辑。另外,我的术语是明确的。问题在于将终结器和析构函数混为一谈,甚至还同时提及了处置程序。传播正确的话值得。
kdbanman 2015年

@kdbanman的困难在于,我正在向你们两个讲话,因为您的回答仅供参考。您不能将引用计数用作范例,因为它是一个较弱的GC,很少在语言中使用(检查OP引用的语言),因此添加终结器实际上很容易(但使用受限)。跟踪收集器几乎总是被使用。但是终结器很难挂在它们上,因为垂死的对象是未知的(与您认为正确的陈述相反)。静态类型与动态类型之间的区别是无关紧要的,通过数据存储的动态类型至关重要。
babou 2015年

@kdbanman关于术语,它通常是有用的,因为它对应于不同的情况。但这并没有帮助,因为问题在于将终结处理转移到GC。基本的GC应该只能进行销毁。所需要的是一种可以区分的术语,getting memory recycled我称之为reclamation,并在此之前进行了一些清理,例如回收其他资源或更新了一些我称为的对象表finalization。这些对我来说似乎是相关的问题,但是我可能错过了您的术语,这对我来说是新的。
babou 2015年

1
谢谢@kdbanman,宝贝。好讨论。我认为您的两个帖子都涵盖了相似的观点。就像你们两个都指出的那样,核心问题似乎是该语言的运行时中使用的垃圾收集器类别。我找到了这篇文章,为我清除了一些误解。似乎更健壮的gcs仅处理低级原始内存,这会使高级对象类型对gc不透明。在不了解内存内部知识的情况下,gc无法破坏对象。这似乎是您的结论。
dbcb

4

对象析构函数模式是系统编程中错误处理的基础,但与垃圾回收无关。相反,它与将对象生存期匹配到范围有关,并且可以在具有一流功能的任何语言中实现/使用。

示例(伪代码)。假设您具有“原始文件”类型,例如Posix文件描述符类型。有四种基本的操作,open()close()read()write()。您想实现一个“安全”文件类型,该文件类型总是在其自身之后进行清理。(即,具有自动构造函数和析构函数。)

我会认为我们的语言中有异常处理用throwtry以及finally(在没有异常处理,你可以设立一个纪凡你的类型的用户返回一个特殊值指示错误的语言。)

您设置了一个接受可以完成工作的功能的功能。worker函数接受一个参数(“安全”文件的句柄)。

with_file_opened_for_read (string:   filename,
                           function: worker_function(safe_file f)):
  raw_file rf = open(filename, O_RDONLY)
  if rf == error:
    throw File_Open_Error

  try:
    worker_function(rf)
  finally:
    close(rf)

您还提供read()和的write()实现safe_file(仅调用raw_file read()write())。现在,用户使用如下safe_file类型:

...
with_file_opened_for_read ("myfile.txt",
                           anonymous_function(safe_file f):
                             mytext = read(f)
                             ... (including perhaps throwing an error)
                          )

C ++析构函数实际上只是一个try-finally块的语法糖。我在这里所做的几乎所有工作就是将safe_file带有构造函数和析构函数的C ++ 类转换为什么。请注意,C ++没有finally例外,特别是因为Stroustrup认为在语法上使用显式析构函数更好(并且在该语言具有匿名功能之前,他将其引入了该语言)。

(这是多年来人们一直在使用类似Lisp的语言进行错误处理的方式的一种简化。我想我是在1980年代末或1990年代初首次遇到它的,但是我不记得在哪里。)


这描述了C ++中基于堆栈的析构函数模式的内部结构,但没有解释为什么垃圾回收语言不会实现这种功能。您可能是对的,这与垃圾回收无关,但是它与一般的对象销毁/最终化有关,这在垃圾回收语言中似乎很困难或效率很低。因此,如果不支持常规销毁,那么基于堆栈的销毁似乎也将被省略。
dbcb 2015年

正如我在开始时所说的:任何具有一流功能(或一流功能的近似)的垃圾收集语言都使您能够提供“ safe_file和” with_file_opened_for_read(如对象超出范围时会自行关闭)的“项目符号证明”接口。 )。这很重要,因为它的语法与构造函数无关。Lisp,Scheme,Java,Scala,Go,Haskell,Rust,Javascript,Clojure都支持足够的一流函数,因此它们不需要析构函数即可提供相同的有用功能。
2015年

我想我明白你在说什么。由于语言提供了基本的构建块(try / catch / finally,一流的函数等)来手动实现类似析构函数的功能,因此它们不需要析构函数吗?为了简单起见,我可以看到一些语言采用这种方式。虽然,这似乎不是列出所有语言的主要原因,但也许就是如此。也许我只是少数热爱C ++析构函数的人,而没有人真正在乎,这很可能就是大多数语言不实现析构函数的原因。他们只是不在乎。
dbcb 2015年

2

这不是问题的完整答案,但我想补充一些其他答案或评论中未提及的观察结果。

  1. 这个问题隐含地假设我们正在谈论一种Simula风格的面向对象的语言,它本身是有限的。在大多数语言中,即使是那些带有对象的语言,也不是所有的东西都是对象。实施析构函数的机制会带来并非所有语言实施者都愿意支付的费用。

  2. C ++对销毁顺序有一些隐含的保证。例如,如果您具有树状数据结构,则子级将在父级之前被销毁。在GC语言中情况并非如此,因此分层资源可能会以不可预测的顺序释放。对于非内存资源,这可能很重要。


2

当设计两个最流行的GC框架(Java和.NET)时,我认为作者希望最终确定能够很好地工作,从而避免了对其他形式的资源管理的需求。如果不需要所有必需的功能来适应100%可靠和确定性的资源管理,则可以大大简化语言和框架设计的许多方面。在C ++中,有必要区分以下概念:

  1. 标识对象的指针/引用,该对象仅由引用的所有者拥有,并且未被所有者不知道的任何指针/引用标识。

  2. 标识不属于任何人的可共享对象的指针/引用。

  3. 指针/参考标识哪些专门的物体所拥有由参考的保持器,而是其可以是通过“视图”的拥有者不跟踪的方式进行访问。

  4. 标识对象的指针/引用,该对象提供了他人拥有的对象的视图。

如果GC语言/框架不必担心资源管理,则可以将所有上述内容替换为一种参考。

我会天真地认为终结确定将消除对其他形式的资源管理的需求,但是从那时起这种期望是否合理,历史表明,在许多情况下,需要比终结提供更精确的资源管理。我碰巧认为,在语言/框架级别识别所有权的奖励足以证明成本(复杂性必须存在于某处,而将其移至语言/框架将简化用户代码),但确实意识到存在很多设计的好处是只有一个“种类”的参考即可—只有在语言/框架与资源清理问题无关的情况下,这种参考才有效。


2

为什么在垃圾收集语言中普遍缺少对象析构函数范式?

我来自C ++背景,所以这个领域令我感到困惑。

C ++中的析构函数实际上将 件事结合在一起。它释放RAM,并释放资源ID。

其他语言通过让GC负责释放RAM而另一种语言功能负责释放资源ID来分离这些问题。

我发现非常缺乏这些语言将内存视为值得管理的唯一资源。

这就是GC的全部意义所在。他们只做一件事,这是确保您不会耗尽内存。如果RAM无限大,则将不再使用所有GC,因为不再存在任何真正的理由。

套接字,文件句柄,应用程序状态如何?

语言可以通过以下方式提供释放资源ID的不同方式:

  • 手册.CloseOrDispose()分散在代码中

  • 手册.CloseOrDispose()分散在手册“ finally块”内

  • 手册“资源ID块”(即usingwithtry-附资源,它能够自动等).CloseOrDispose()的块之后完成

  • 有保证的“资源ID块”,.CloseOrDispose()完成块后自动执行

许多语言使用手动(而不是保证)机制,这为资源管理不善创造了机会。使用以下简单的NodeJS代码:

require('fs').openSync('file1.txt', 'w');
// forget to .closeSync the opened file

..程序员忘记关闭打开的文件的位置。

只要程序继续运行,打开的文件就会陷入困境。通过尝试使用HxD打开文件并验证无法完成,很容易验证:

在此处输入图片说明

也不保证在C ++析构函数中释放资源ID。您可能会认为RAI​​I像保证的“资源ID块”一样工作,但是与“资源ID块”不同,C ++语言不会阻止提供RAII块的对象停止泄漏,因此RAII块可能永远不会完成


似乎几乎所有具有OOPy对象支持的现代垃圾收集语言(如Ruby,Javascript / ES6 / ES7,Actionscript,Lua等)都完全省略了析构函数/定型化范例。Python似乎是唯一使用其class __del__()方法的人。为什么是这样?

如上所述,因为它们使用其他方式管理资源ID。

什么是语言设计决策,这些决策导致这些语言无法执行对象处置的自定义逻辑?

如上所述,因为它们使用其他方式管理资源ID。

为什么一种语言会具有带有类或类结构以及自定义实例化(构造函数)的对象实例的内置概念,却完全省略了销毁/最终确定功能?

如上所述,因为它们使用其他方式管理资源ID。

我可以看到一个可能的论点是,直到将来某个不确定的时间,析构函数/ finalizer才会被调用,但这并没有阻止Java或Python支持该功能。

Java没有析构函数。

Java文档提到

但是,finalize的通常目的是在清除对象之前将其清除。例如,代表输入/输出连接的对象的finalize方法可能会执行显式I / O事务,以在永久丢弃该对象之前中断连接。

..但是将资源ID管理代码放进去Object.finalizer是很大程度上被视为反模式(请参阅参考资料)。这些代码应改为在呼叫站点上编写。

对于使用反模式的用户,其理由是他们可能忘记了在呼叫站点释放资源ID。因此,以防万一,他们在终结器中再次这样做。

不支持任何形式的对象完成的核心语言设计原因是什么?

终结器的用例并不多,因为它们用于在不再有对该对象的任何强引用之间以及该对象的内存被GC回收之间的这段时间之间运行一段代码。

一个可能的用例是,当您想记录GC收集对象之间的时间到不再有对该对象的任何强引用的时间的日志时,例如:

finalize() {
    Log(TimeNow() + ". Obj " + toString() + " is going to be memory-collected soon!"); // "soon"
}

-1

在Dobbs wrt c ++中找到了对此的参考,该参考文献具有更普遍的观点,认为析构函数在实现它们的语言中存在问题。这里的一个大概想法似乎是,析构函数的主要目的是处理内存释放,而这很难正确完成。内存是分段分配的,但是连接了不同的对象,因此释放的责任/界限不是很清楚。

因此,垃圾收集器的解决方案是在几年前发展起来的,但是垃圾收集不是基于方法退出时作用域中消失的对象(这是一个很难实现的概念性想法),而是基于周期性运行的收集器(某种程度上不确定),当应用程序遇到“内存压力”(即内存不足)时。

换句话说,从某种意义上说,纯粹的人类概念“新未使用的对象”在某种意义上是一种误导性抽象,即没有任何对象可以“即刻”变为未使用。只能通过运行遍历对象参考图的垃圾回收算法来“发现”未使用的对象,而性能最佳的算法会间歇运行。

可能有一种更好的垃圾回收算法正在等待被发现,该算法可以近乎瞬时地识别出未使用的对象,这可能导致一致的析构函数调用代码,但是在该领域进行了多年的研究后仍未找到。

资源管理区域(例如文件或连接)的解决方案似乎是具有尝试处理其使用的对象“管理器”。


2
有趣的发现。谢谢。作者的论据基于析构函数在错误的时间被调用的原因,因为在类没有适当的复制构造函数的情况下,通过类实例的值传递值(这是一个实际的问题)。但是,这种情况在大多数(如果不是全部)现代动态语言中实际上并不存在,因为所有内容都是通过引用传递的,从而避免了作者的情况。尽管这是一个有趣的观点,但我认为这不能解释为什么大多数垃圾收集语言都选择省略析构函数/完成功能。
dbcb 2015年

2
这个答案曲解了Dobb博士的文章:该文章并不认为析构函数通常是有问题的。这篇文章实际上争论了这一点:内存管理原语就像goto语句一样,因为它们既简单又功能强大。就像goto语句最好封装在“适当限制的控制结构”中一样(请参见:Dijktsra),内存管理原语最好封装在“适当限制的数据结构”中。析构函数是朝这个方向迈出的一步,但还远远不够。 自己决定是否正确。
kdbanman
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.