【问题标题】:Why can't C++ have an optional transparent garbage collector为什么 C++ 不能有一个可选的透明垃圾收集器
【发布时间】:2010-12-29 21:26:48
【问题描述】:

有一个相关问题,但这个问题略有不同,我对相关问题的任何答案都不满意 :)

我将否定这个问题,断言不可能为 C++ 提供可选的透明垃圾收集器,并希望有人能证明我错了。是的,Stroustrup 对此进行了尝试,但多次失败不是因为技术问题,而是因为一致性问题。性能在这里不是问题。

C++ 永远不会有这样一个收集器的原因是,作为一个可选,没有收集器运行的程序必须手动实现所有必需的内存管理。添加收集器可能会提供一些性能优势,但尚不清楚它们是否值得(是的,收集器可以更快)。

您无法获得的是自动内存管理,这是需要收集器的主要原因。您将通过强制收集获得此信息(如果您选择进行正确的手动管理,则不必牺牲 RAII 或其他东西)。具有可选手动内存管理的强制收集器是可行的。

不幸的是,获得强制收集器的唯一方法与不使用收集器的早期版本的 C++ 不兼容:换句话说,如果我们想要自动透明的内存管理,我们必须定义一种新语言。

所以我的观点是:C++ 永远不会有垃圾收集,因为它被锁定在一个需要向上兼容的历史发展中:强制收集与可选的手动内存管理是可行的,但透明的可选垃圾收集是不可行的。

通过展示一个可靠的可选透明垃圾收集模型来证明我错了!

编辑:

Oooo .. 我想我有答案了。有人可以引用标准要求程序删除堆分配的对象吗?

因为:该子句(如果存在)是唯一阻止可选透明垃圾收集的事情。甚至可能有足够的时间从 C++1x 中删除该子句。

如果没有这样的子句,程序可能会泄漏内存而行为未定义:内存不足时的行为与通常情况相同。因此,添加垃圾收集器不会对指定的语义产生任何影响:它们是否定义明确,与是否使用收集器无关。

【问题讨论】:

  • 我相信它叫C++/CLI ;-) 当然是在使用“句柄”时。
  • 标准需要相应地调用delete IIRC——尽管如果你不这样做,你的电脑会讨厌你。
  • @Billy:它不需要删除,你是对的。需要的是new-ed 对象的释放必须使用delete。但是,当然,继续。如果您愿意,请泄漏。 :)
  • 真的吗?不需要销毁每个构造的对象吗?然后我们已经可以有可选的集合了。故意泄漏的程序可以解决小问题,而需要收集器来解决更大的问题。
  • 不调用对象的析构函数确实是未定义的。然而,标准并没有定义用户应该调用析构函数,而不是 GC,也没有定义应该发生这种情况的任何时间范围。未能删除新对象在 C++03 中未定义,但在 C++0x 中定义了实现。

标签: c++ garbage-collection


【解决方案1】:

通过展示一个可靠的可选透明垃圾收集模型来证明我错了!

请参阅:C++/CLI

将垃圾收集器与现有 C++ 代码一起使用的困难在于,C++ 通常依赖确定性对象破坏来使事情发生;就像在 RAII 中所做的那样。当然,垃圾收集器可以使大多数类型的内存 RAII 透明,但是很多与 RAII 相关的概念与内存没有任何关系。例如,套接字、流和锁都适用于某种形式的 RAII 管理,如果不保留确定性破坏,这些都不会很好地工作。

因此,它可能不会是“透明的”——它必须是 C++/CLI 之类的东西,你必须说“我希望它被垃圾收集”——但这绝对是合理的,而且可能。

【讨论】:

  • +1 你偷了我的评论——或者我发表了你的答案;-)
  • C++/CLI 不是透明的吗?
  • @Yttrill:“透明”是什么意思?如果“透明”是指“现有代码可以正常工作”,那么是的,它是透明的。如果您的意思是“所有现有代码现在都被隐式 gc'd”,那么没有。
  • 自动分配(栈分配)对象和新分配(堆分配)对象是有区别的。您可以轻松地为前者保留确定性破坏,但为后者使用垃圾收集。
  • 顺便说一句:RAII 没有问题:它的工作方式与现在相同。真正的问题是您“不允许”泄漏内存。如果你可以泄漏,可选的收集来清除泄漏就可以了。嗯..你可以泄露吗?
【解决方案2】:

这可能更适合作为评论而不是答案,并且可能会引起很多反对意见。就这样吧。

每当有人问“为什么 C++ 不能有 GC?”之类的问题时。我对自己说“因为不想要你该死的垃圾收集。我想控制对象何时存活。我想控制对象何时死亡。我希望销毁和释放是确定性的,而不是基于关于一些 hokus pokus 的黑魔法。我不需要 GC 来编写更好的程序。因此,GC 不会为我做任何事情,只会妨碍我。”

但除此之外,请考虑这一点。 C# 和其他 .NET 语言都内置了 GC。这些语言的编译器和 CLR 主要是用 C++ 编写的。这包括内存管理工具,除了一些用汇编程序编写的对性能至关重要的部分。

所以你可能会说,C# 能做的任何事情,C++ 都能做,因为 C++ 诞生了 C#。

来吧,投反对票...

【讨论】:

  • @John:您的声誉足够高,您甚至不会注意到投反对票 :) 但是请注意这里的概念不是要处理手动内存管理。真正的问题是:一个定义明确的程序会泄漏吗?当我提出这个问题时,我没有意识到这一点。写一个字符串类或者单链表类并且有一个no-op析构函数可以吗?
  • 在这个答案中加入一些牙齿,解释你如何避免使用 shared_ptr。
  • @Hans:不确定我是否理解该评论。你可以通过简单地不去删除任何东西来避免它,除非它直接或间接地指向一个具有操作系统资源的对象,例如文件句柄,你真正关心的是关闭文件。
  • 我很确定约翰明白我的意思。去约翰!
  • @Hans: shared_ptr 仍然是确定性的。您可以准确定义它决定对其内容进行核对的时间和地点。在 GC 领域并非如此。
【解决方案3】:

“为什么我的兰博基尼没有雪犁刀片支架?”因为它不是为除雪而设计的;)

C++ 的设计不像 C# 并且有不同的用途。为正确的工作使用正确的工具,生活就会轻松很多。

【讨论】:

  • 是的,但没有合适的工具,而且通常是如何调整现有工具以使其更有用的问题。缺乏自动内存管理是程序员放弃 C++ 转而使用 Java 或其他更糟糕的语言的主要原因之一。
  • @Yttrill: Err.. 我认为将 C++ 称为“废弃”是相当极端的。大学已经远离它了一点,但不是整个编程行业。另外,我认为使用 Java 或 C# 之类的更大原因是这些语言包含的巨大标准库的结果,而不是因为 GC。现有的 C++ 智能指针有效地解决了 C++ 中的内存管理问题。
  • @Billy:是的,我应该说“许多程序员放弃了 C++”。我同意,原始 Java 的一大亮点是可移植的 GUI。
  • @Yttril:“没有合适的工具”。这个论点自相矛盾。您还提出所有程序员出于相同原因做出相同选择的论点?抱歉,不敢相信。
【解决方案4】:

我可以举一些 C++ 的可选透明垃圾收集器的例子来证明你错了吗?

还有a good discussion on the Boehm site 是此类问题的必读内容。

【讨论】:

  • libgc 是 boehm,所以这只是一个例子 :) 我对 Boehm 很熟悉。我同意这或多或少是透明的。这里的问题是:如果你不能强制使用它,你能不能写一个泄露一些东西并依赖 gc 的程序?
  • @Yttrill:谁说你不能强制使用它?好的,因此您的程序将可移植到“具有符合标准的 C++ 编译器、足够的内存、足够的磁盘空间和 boehm 实现的平台”,而不是“具有符合标准的 C++ 编译器、足够的内存和足够的磁盘的所有平台”空间”。对于大多数程序(以及几乎所有 gc 可行的程序),这根本没有限制。
  • 没有强制要求的是符合标准的编译器必须带有垃圾收集器。但是一个程序并不会仅仅因为它依赖于boost而变得不合格,同样它也不会因为依赖libgc而变得不合格。
  • @Ben:“强制使用”我的意思是要求 C++ 编译器提供收集器。我认为这个要求会阻止 C++ 在许多应用程序中使用,这就是为什么我们也有 new with nothrow 选项。也许强制收集器是站得住脚的,但我的问题假设不是:底线是强制收集器将面临政治问题,而可选的则不会(我们在这里讨论纳入标准)。
  • 顺便说一句:不清楚您是否将关于不符合项的声明更改为“库”而不是程序。如果你有一个依赖于 libgc 的库 A,你不能将它与我的库 B 一起使用,这会排除它。我实际上有这样一个库,它与 Boehm 收集器不兼容(因为它隐藏了指针,并且可能使它们在不存在时看起来无法访问)。
【解决方案5】:

我认为您采取了错误的方法。 GC 没有理由应该是透明的 - 为什么不使用 std::gc_pointer<T>

您需要考虑 GC 的真正目的。这不是为了解决内存管理,因为现有的智能指针(在 C++0x 中)很好地解决了这个问题 - 它提供了与手动内存管理不同的性能特征,非常适合临时分配。为什么不只是有一个 std::gc_new 呢?我们已经有了一个 std::make_shared。

而且,在 C++0x 中,是否自动删除未删除的对象已经由实现定义。

【讨论】:

  • 如果我错了,请原谅我,但智能指针根本不能解决它:它们有两个问题:一个是性能,它们比 gc 慢。另一个更基本:它们不处理周期。有充分的理由希望透明收集:您可以忘记内存管理,您只需要手动管理非内存资源。这使代码更简单、更可靠。
  • 顺便说一句:我确实有一个相当于 std::gc_new 的系统。然而,它是一个精确的收集器,它不会钩住 malloc。所以它不能在 std::vector 中追踪 gc'd 指针。
  • @Yttrill:存在引用循环是因为您没有正确设计程序,并且消除它们发生在设计时,而不是运行时。更重要的是,糟糕的所有权语义会影响管理之外的地方,例如并发性。我承认,在只存在复制语义的 C++03 中,这可能很困难,但是对于移动语义,引用循环不应该存在。我相信智能指针实际上比 GC 更快——视情况而定。我的帖子提倡允许这两种解决方案,因为我相信它们擅长不同的任务。
  • 我还想补充一点,仅仅为内存解决资源管理是一个相当无用的好处。毕竟,我现在必须去解决其他十几个资源。
  • 这不是一个无用的好处:它是资源管理的主要部分,它还消除了一些悬空指针取消引用问题的情况。是的,你必须为其他资源解决它,但无论如何你必须这样做。这不是什么大问题,我一直在 Ocaml 中这样做。这些资源中的大多数都是原子的:它们之间没有像内存那样复杂的关系。
【解决方案6】:

真正的问题不在于确保对象销毁确定性地发生(这可能很容易做到:当对象超出范围时(或者在堆分配对象的情况下调用delete),可以调用它的析构函数,而实际的内存回收可以留到以后的垃圾收集)——而是如何识别要收集的什么

为此,GC 需要能够遍历对象图。 在“正确”的 GC 语言中,这很简单,因为每个对象都被标记了某种类型的指针,允许 GC 知道它正在访问的对象的结构。

在 C++ 中,通常没有这样的东西。 GC 无法知道它正在查看的单词是否是指针,同样重要的是,next 单词是否是同一结构/数组的一部分,或者是否它是未分配的。

当然,标准并没有禁止实现添加此类类型信息,但这会带来运行时性能和内存使用方面的成本,这与 C++ 的“您只需为使用的内容付费”的理念不兼容。

现有的 GC 采用的另一种选择是实现保守的 GC,它可能不会回收 所有内存,因为它必须猜测一个单词是否是指针,当有疑问时,它必须是悲观的。

【讨论】:

  • 您可能会感到惊讶。 Boehm 是保守的,因此它不需要任何信息,尽管您可以通过提供一些信息来帮助它。我在 Felix 中使用的幼稚收集器是精确的,但典型的程序不需要担心那么多形状对象。此外,我不仅可以非常快速地判断指针是否指向对象,还可以判断它是否指向对象内部的任何位置!这在分配时间和空间方面有一点成本,其中一些只是因为 C 太笨了,无法提供获取分配对象长度的函数(malloc 必须知道)。
  • @Yttrill:本机堆分配的大小不一定相同。如果您分配一个 char,那么 malloc 可能会返回足够的 int- 但您仍然应该只访问 char,因为 int 的其余部分不会被初始化等等。
猜你喜欢
  • 1970-01-01
  • 2022-01-12
  • 1970-01-01
  • 1970-01-01
  • 2020-03-06
  • 1970-01-01
  • 2023-03-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多