当前位置:  开发笔记 > 编程语言 > 正文

为什么C++没有垃圾收集器?

如何解决《为什么C++没有垃圾收集器?》经验,为你挑选了10个好方法。

我不是问这个问题,因为垃圾收集的优点首先.我提出这个问题的主要原因是我知道Bjarne Stroustrup已经说过C++在某些时候会有一个垃圾收集器.

话虽如此,为什么还没有添加?已经有一些垃圾收集器用于C++.这只是那些"说起来容易做起来难"的事情吗?还是有其他原因没有添加(并且不会在C++ 11中添加)?

交叉链接:

用于C++的垃圾收集器

为了澄清,我理解为什么C++在第一次创建时没有垃圾收集器的原因.我想知道为什么收藏家不能加入.



1> Brian R. Bon..:

可能已经添加了隐式垃圾收集,但它只是没有削减.可能由于不仅仅是实施并发症,而且还因为人们无法以足够快的速度达成普遍共识.

Bjarne Stroustrup本人的一句话:

我曾希望可以选择启用的垃圾收集器将成为C++ 0x的一部分,但是我有足够的技术问题只需详细说明这种收集器如何与其他语言集成,如果提供.与基本上所有C++ 0x特征的情况一样,存在实验性实现.

有话题商量好了这里.

总体概述:

C++非常强大,几乎可以做任何事情.因此,它不会自动将很多东西推到您身上,这可能会影响性能.使用智能指针(包含带引用计数的指针的对象)可以很容易地实现垃圾收集,当引用计数达到0时自动删除它们.

C++是在没有垃圾收集的情况下构建的.与C和其他人相比,效率是C++不得不抵制批评的主要问题.

垃圾收集有两种类型......

显式垃圾收集:

C++ 0x将通过使用shared_ptr创建的指针进行垃圾收集

如果你想要它你可以使用它,如果你不想要它你不会被迫使用它.

如果您不想等待C++ 0x,您当前也可以使用boost:shared_ptr.

隐式垃圾收集:

它虽然没有透明的垃圾收集.不过,它将成为未来C++规范的焦点.

为什么Tr1没有隐式垃圾收集?

C++ 0x的tr1应该有很多东西,Bjarne Stroustrup在之前的访谈中表示tr1没有他想要的那么多.


如果C++强制垃圾收集我,我会**成为一个仇恨者!为什么人们不能使用`smart_ptr'?如何使用垃圾收集器进行低级Unix风格分叉?其他因素会受到影响,例如线程.Python有**全局解释器锁**,主要是因为它的垃圾收集(参见Cython).保持它不受C/C++的影响,谢谢.
@ unixman83:引用计数垃圾收集的主要问题(即`std :: shared_ptr`)是循环引用,导致内存泄漏.因此,你必须小心地使用`std :: weak_ptr`来打破循环,这很麻烦.标记和扫描样式GC没有这个问题.线程/分叉和垃圾收集之间没有固有的不兼容性.Java和C#都具有高性能抢占式多线程和垃圾收集器.实时应用程序和垃圾收集器存在问题,因为大多数垃圾收集器必须阻止全世界运行.
"你会怎么做低级Unix风格的分叉".像OCaml这样的GC语言已经做了大约20年或更长时间.
"参考计数垃圾收集的主要问题(即`std :: shared_ptr`)是循环引用"和可怕的性能,这是具有讽刺意味的,因为更好的性能通常是使用C++的理由...... http://flyingfrogblog.blogspot.co .UK/2011/01 /升压-sharedptr向上至10慢than.html
"Python的全局解释器锁主要是因为它的垃圾收集".斯特劳曼的论点.Java和.NET都有GC,但都没有全局锁.

2> Matthieu M...:

在这里增加辩论.

垃圾收集存在已知问题,了解它们有助于理解C++中没有垃圾收集的原因.

1.表现?

第一个抱怨往往是关于表现,但大多数人并没有真正意识到他们在谈论什么.如图所示,Martin Beckett问题可能不是性能本身,而是性能的可预测性.

目前有2个GC系列广泛部署:

Mark-And-Sweep kind

参考 - 计数种类

Mark And Sweep快(对整体性能的影响较小),但它从一个"冻结世界"遭受综合征:即当在,其他一切都停止在GC踢,直到GC作出了清理.如果你想构建一个在几毫秒内回答的服务器...某些交易将达不到你的期望:)

问题Reference Counting是不同的:引用计数会增加开销,尤其是在多线程环境中,因为您需要具有原子计数.此外还存在参考周期的问题,因此您需要一个聪明的算法来检测这些周期并消除它们(通常通过"冻结世界"来实现,尽管不那么频繁).一般来说,截至今天,这种类型(即使通常更敏感或更确切地说,冷冻不那么频繁)比慢Mark And Sweep.

我看过埃菲尔实施者的一篇论文,他们试图实现一个Reference Counting垃圾收集器,它具有类似的全球性能而Mark And Sweep没有"冻结世界"的方面.它需要一个单独的GC线程(典型值).这个算法有点令人恐惧(最后),但是本文很好地一次介绍了一个概念,并展示了算法从"简单"版本到完整版本的演变.推荐阅读,只要我能把手放回PDF文件......

2.资源获取是初始化

这是一个常见的习惯用法C++,你将把资源的所有权包装在一个对象中,以确保它们被正确释放.它主要用于内存,因为我们没有垃圾收集,但它在许多其他情况下也很有用:

锁(多线程,文件句柄,......)

连接(到数据库,另一台服务器......)

我们的想法是正确控制对象的生命周期:

只要你需要它就应该活着

当你完成它应该被杀死

GC的问题在于,如果它有助于前者并最终保证以后......这种"终极"可能还不够.如果你发布一个锁,你真的希望它现在被释放,这样它就不会阻止任何进一步的调用!

GC的语言有两种解决方法:

堆栈分配足够时不要使用GC:它通常用于性能问题,但在我们的情况下它确实有帮助,因为范围定义了生命周期

using构造...但它是显式(弱)RAII而在C++中RAII是隐式的,因此用户不会无意中犯错(通过省略using关键字)

3.智能指针

智能指针通常表现为处理内存的银弹C++.我经常听说过:毕竟我们不需要GC,因为我们有智能指针.

一个人不能错.

智能指针确实有帮助:auto_ptrunique_ptr使用RAII概念,确实非常有用.它们非常简单,你可以很容易地自己编写它们.

当需要共享所有权时,它会变得更加困难:您可能在多个线程之间共享,并且计数的处理存在一些微妙的问题.因此,人们自然会走向shared_ptr.

这很棒,毕竟这就是Boost,但它不是一颗银弹.事实上,主要的问题shared_ptr是它模拟了一个GC实现,Reference Counting但你需要自己实现循环检测... Urg

当然有这个weak_ptr东西,但遗憾的是我已经看到了内存泄漏,尽管shared_ptr因为这些循环而使用......当你处于多线程环境时,它很难被发现!

4.解决方案是什么?

没有银弹,但一如既往,它绝对可行.在没有GC的情况下,需要明确所有权:

如果可能的话,我希望在一个给定时间拥有一个所有者

如果没有,请确保您的类图没有任何与所有权相关的循环,并以微妙的应用程序打破它们 weak_ptr

事实上,拥有一个GC会很棒......但这不是一个小问题.与此同时,我们只需要卷起袖子.


只有两种?怎么'复制收藏家?世代收藏家?各种并发收藏家(包括贝克的硬实时跑步机)?各种混合收藏家?男人,这个领域的纯粹无知有时令我惊讶.
我说只有两种类型吗?我说有2个被广泛部署.据我所知,Python,Java和C#现在都使用Mark和Sweep算法(Java曾经有过引用计数算法).更准确地说,在我看来,C#使用Generational GC进行较小的周期,Mark和Sweep用于主要周期和复制以抵御内存碎片; 虽然我认为算法的核心是Mark And Sweep.你知道使用其他技术的主流语言吗?我总是很乐意学习.
你刚刚命名了一种使用三种语言的主流语言.
主要区别在于Generational和Incremental GC不需要停止世界工作,并且您可以通过在访问GC指针时偶尔执行树遍历的迭代来使它们在单线程系统上工作而不会产生太多开销(因素)可以通过新节点的数量以及需要收集的基本预测来确定.您可以通过包含有关节点创建/修改的代码位置的数据来进一步获取GC,这可以让您改进预测,并且可以免费获得Escape Analysis.
我希望我能接受两个答案!这太棒了.有一点需要指出的是,就性能而言,在单独的线程中运行的GC实际上很常见(它在Java和.Net中使用).当然,这在嵌入式系统中可能是不可接受的.

3> Martin Becke..:

什么类型?它应该针对嵌入式洗衣机控制器,手机,工作站还是超级计算机进行优化?
它应该优先考虑gui响应能力还是服务器负载?
它应该使用大量内存还是大量CPU?

C/c ++用于太多不同的情况.我怀疑像升级智能指针这样的东西对大多数用户来说已经足够了

编辑 - 自动垃圾收集器不是一个性能问题(你总是可以买更多的服务器)这是一个可预测的性能问题.
不知道GC什么时候开始就像雇用一个嗜睡的航空公司飞行员,大部分时间他们都很棒 - 但是当你真的需要响应时!


不,Java不适合高性能应用程序,原因很简单,它没有与C++相同程度的性能保证.所以你会在手机中找到它,但你不会在手机开关或超级计算机中找到它.
您可以随时购买更多服务器,但您不能总是为已经在客户口袋中的手机购买更多CPU!
Java在CPU效率方面已经做了很多性能追赶.真正难以处理的问题是内存使用,Java本质上比C++更低的内存效率.这种低效率是由于它是垃圾收集的事实.垃圾收集不能既快速又节省内存,如果你研究一下GC算法的工作速度,这个事实就变得很明显了.
我肯定看到了你的观点,但我觉得有必要问:Java是不是在几乎同样多的应用程序中使用?
@Zathrus java可以在优化jit的吞吐量b/c上获胜,但不是延迟(boo real-time),当然也不是内存占用.
@Zathrus - Java on Mainframe:http://www-03.ibm.com/systems/z/os/zos/tools/java/.Java交换机控制器 - http://floodlight.openflowhub.org/.实时性能保证仅对相对较小的高性能应用程序子集很重要,在大多数HPC领域,Java长期以来一直很强大.

4> Greg Rogers..:

C++没有内置垃圾收集的最大原因之一是让垃圾收集与析构函数一起使用真的很难.据我所知,没有人真正知道如何完全解决它.有很多问题需要处理:

对象的确定性生命周期(引用计数给你这个,但GC没有.虽然它可能不是那么大的交易).

如果在对象被垃圾收集时析构函数抛出会发生什么?大多数语言都忽略了这个异常,因为它确实没有阻止能够传输它,但这可能不是C++可接受的解决方案.

如何启用/禁用它?当然,它可能是一个编译时决定,但是为GC编写的代码和为非GC编写的代码将会非常不同并且可能不兼容.你如何调和这个?

这些只是面临的一些问题.


GC和析构函数是一个解决的问题,来自Bjarne的一个很好的回避.析构函数不会在GC期间运行,因为这不是GC的要点.C++中的GC存在以创建无限_memory_的概念,而不是无限的其他资源.
这是一个虚假的论点.由于C++具有显式的内存管理,因此您需要确定何时必须释放每个对象.使用GC,情况并不差; 相反,问题被简化为找出某些对象何时被释放,即那些在删除时需要特别考虑的对象.体验Java和C#编程表明绝大多数对象不需要特殊考虑,可以安全地留给GC.事实证明,C++中析构函数的一个主要功能是释放子对象,GC会自动为您处理.
如果析构函数不运行那么完全改变了语言的语义.我想至少你需要一个新的关键字"gcnew"或类似的东西,以便你明确允许这个对象被GC(因此你不应该用它来包装除了内存之外的资源).
@ NateC-K:在GC与非GC(可能是最重要的事情)中得到改进的一件事是固体GC系统能够保证只要参考存在,每个参考将继续指向同一个对象.在一个对象上调用`Dispose`可能会使它无法使用,但是当它活着时指向该对象的引用将在它死后继续这样做.相比之下,在非GC系统中,可以在引用存在时删除对象,并且如果使用其中一个引用,则很少会对可能造成的破坏进行任何限制.

5> Jerry Coffin..:

虽然这是一个问题,但仍有一个问题我根本没有看到任何人解决过:垃圾收集几乎无法指定.

特别是,C++标准非常谨慎地根据外部可观察行为来指定语言,而不是实现如何实现该行为.在垃圾回收的情况下,然而,几乎没有外部观察到的行为.

垃圾收集的一般思想是它应该合理地尝试确保内存分配成功.不幸的是,即使你有垃圾收集器在运行,基本上也不可能保证任何内存分配都会成功.在任何情况下都是如此,但在C++的情况下尤其如此,因为它(可能)不可能使用在收集周期期间在内存中移动对象的复制收集器(或类似的东西).

如果您无法移动对象,则无法创建一个连续的内存空间来进行分配 - 这意味着您的堆(或免费存储,或者您喜欢称之为的任何内容)可以,并且可能会随着时间的推移变得支离破碎.反过来,这可以防止分配成功,即使存在比所请求的数量更多的内存.

虽然有可能提出一些保证说(实质上)如果重复完全重复相同的分配模式,并且第一次成功,它将继续在后续迭代中成功,前提是分配的内存在迭代之间变得无法访问.这是一个如此薄弱的保证,它本质上是无用的,但我看不出任何加强它的合理希望.

即使这样,它也比C++提出的要强.在先前的提案 [警告:PDF](即得到下降)并不能保证在所有的东西.在28页的提案中,你对外部可观察行为的看法是单一(非规范性)说明:

[注意:对于垃圾收集程序,高质量的托管实现应该尝试最大化它回收的无法访问的内存量. - 尾注]

至少对我而言,这引发了一个关于投资回报的严重问题.我们将破坏现有的代码(没有人确切地确定了多少,但绝对相当多),对实现和代码的新限制提出了新的要求,而我们获得的回报很可能一点都没有?

即便充其量,我们得到的是基于Java测试的程序,可能需要大约六倍的内存才能以与现在相同的速度运行.更糟糕的是,垃圾收集从一开始就是Java的一部分--C++对垃圾收集器施加了更多的限制,它几乎肯定会有更差的成本/收益比(即使我们超出了提案所保证的范围并假设会有一些好处).

我将以数学方式总结这种情况:这是一个复杂的情况.正如任何数学家所知,复数有两部分:真实的和虚构的.在我看来,我们这里所拥有的是真实的成本,但是(至少大部分)想象的好处.


即使在Java中,GC也没有真正指定做任何有用的AFAIK.它可能会为你称为"免费"(我指的是与C语言一样的"免费").但Java永远不会保证调用终结器或类似的东西.实际上,C++比Java更多地运行提交数据库写入,刷新文件句柄等等.Java声称拥有"GC",但Java开发人员必须始终一丝不苟地调用`close()`并且他们必须非常了解资源管理,小心不要太快或太晚调用`close()`.C++将我们从中解放出来....(继续)
我刚才的评论并不打算批评Java.我只是观察"垃圾收集"一词是一个非常奇怪的术语 - 它意味着比人们想象的要少得多,因此很难在不清楚其含义的情况下进行讨论.

6> Rayne..:

如果你想要自动垃圾收集,那么C++就有很好的商业和公共域垃圾收集器.对于适合垃圾收集的应用程序,C++是一种优秀的垃圾收集语言,其性能可与其他垃圾收集语言相媲美.有关C++中自动垃圾收集的讨论,请参阅C++编程语言(第3版).另见Hans-J.Boehm的C和C++垃圾收集站点.此外,C++支持编程技术,允许内存管理安全和隐式,而无需垃圾收集器.

资料来源:http://www.stroustrup.com/bs_faq.html#garbage-collection

至于为什么它没有内置,如果我没记错,它是在GC之前发明,我不相信该语言可能有GC有几个原因(IE Backwards与C的兼容性)

希望这可以帮助.



7> Aaron McDaid..:

Stroustrup在2013 Going Native会议上对此做了一些很好的评论.

在此视频中,请跳至约25分50秒.(我建议实际观看整个视频,但这会跳过有关垃圾收集的内容.)

如果你有一个非常好的语言,可以直接(安全,可预测,易于阅读,易于教授)直接处理对象和值,避免(显式)使用堆,然后你甚至不想要垃圾收集.

使用现代C++,以及我们在C++ 11中所拥有的东西,除了在有限的情况下,垃圾收集不再是可取的.事实上,即使一个好的垃圾收集器被构建到一个主要的C++编译器中,我认为它不会经常使用.避免GC 会更容易,而不是更难.

他展示了这个例子:

void f(int n, int x) {
    Gadget *p = new Gadget{n};
    if(x<100) throw SomeException{};
    if(x<200) return;
    delete p;
}

这在C++中是不安全的.但它在Java中也不安全!在C++中,如果函数提前返回,delete则永远不会调用.但是,如果你有完整的垃圾收集,例如在Java中,你只是得到一个建议,即"在未来的某个时刻"对象将被破坏(更新:这更糟糕的是.这不是 Java.承诺永远打电话给终结者 - 也许永远不会被召唤.如果Gadget持有一个打开的文件句柄,或者一个数据库连接,或者你为了稍后写入数据库而缓冲的数据,这就不够了.我们希望Gadget在完成后立即销毁,以便尽快释放这些资源.您不希望数据库服务器在数千个不再需要的数据库连接中挣扎 - 它不知道您的程序已完成工作.

那么解决方案是什么?有几种方法.您将用于绝大多数对象的显而易见的方法是:

void f(int n, int x) {
    Gadget p = {n};  // Just leave it on the stack (where it belongs!)
    if(x<100) throw SomeException{};
    if(x<200) return;
}

这需要更少的字符来键入.它没有new妨碍.它不需要您键入Gadget两次.该对象在函数结束时被销毁.如果这是你想要的,这是非常直观的. Gadgets表现与int或相同double.可预测,易于阅读,易于教学.一切都是'价值'.有时价值很高,但价值观更容易教,因为你没有用指针(或引用)得到的"远距离"动作.

您创建的大多数对象仅用于创建它们的函数,并且可能作为输入传递给子函数.程序员在返回对象时不必考虑"内存管理",或者在软件的广泛分离部分之间共享对象.

范围和寿命很重要.大多数情况下,如果寿命与范围相同则更容易.它更容易理解,更容易教学.当你想要一个不同的生命周期时,通过使用shared_ptr例如,阅读你正在做的这些代码应该是显而易见的.(或者按值返回(大)对象,利用移动语义或unique_ptr.

这似乎是一个效率问题.如果我想从中返回一个小工具foo()怎么办?C++ 11的移动语义使得返回大对象变得更容易.只需写Gadget foo() { ... },它就会起作用,并且可以快速工作.你不需要弄乱&&自己,只需按价值返回东西,语言通常就能做必要的优化.(甚至在C++ 03之前,编译器在避免不必要的复制方面做得非常好.)

正如Stroustrup在视频中的其他地方所说的那样:"只有计算机科学家会坚持复制一个物体,然后摧毁原件.(观众笑).为什么不直接将物体移动到新的位置?这就是人类(不是计算机科学家)期望的."

当您可以保证只需要一个对象的副本时,就可以更容易地理解对象的生命周期.您可以选择所需的生命周期策略,如果需要,可以使用垃圾收集.但是当您了解其他方法的好处时,您会发现垃圾收集位于首选项列表的底部.

如果这对您不起作用,您可以使用unique_ptr或失败shared_ptr.在内存管理方面,编写得很好的C++ 11比其他许多语言都更短,更容易阅读,也更容易教授.



8> Uri..:

C++背后的想法是,您不会为不使用的功能支付任何性能影响.因此,添加垃圾收集意味着让一些程序直接在硬件上以C的方式运行,某些程序在某种运行时虚拟机中运行.

没有什么能阻止您使用绑定到某些第三方垃圾收集机制的某种形式的智能指针.我似乎记得微软用COM做了类似的事情,并没有顺利进行.


我同意.你不需要一个虚拟机,但是第二个你开始有一些东西为你管理你的记忆就像在后台一样,我觉得你已经离开了实际的"电线"并且有一种虚拟机情况.
我不认为GC需要VM.编译器可以向所有指针操作添加代码以更新全局状态,而单独的线程在后台运行,根据需要删除对象.

9> Nemanja Trif..:

要回答关于C++的大多数"为什么"的问题,请阅读C++的设计和演变



10> einpoklum - ..:

因为现代C++不需要垃圾收集.

Bjarne Stroustrup 关于此事的常见问题答案说:

我不喜欢垃圾.我不喜欢乱扔垃圾.我的理想是通过不产生任何垃圾来消除对垃圾收集器的需求.现在这是可能的.


对于这些天编写的代码(C++ 17并遵循官方 核心指南),情况如下:

大多数与内存所有权相关的代码都在库中(特别是那些提供容器的代码)

大多数使用涉及内存所有权的代码遵循RAII 模式,因此在构造和销毁时释放时进行分配,这在退出分配内容的范围时发生.

您没有直接显式分配或释放内存.

原始指针不拥有内存(如果你遵循了指导原则),所以你不能通过传递它们来泄漏.

如果你想知道如何在内存中传递值序列的起始地址 - 你将用一个跨度来做; 没有原始指针需要.

如果你真的需要一个拥有"指针",你使用C++的 标准库智能指针 - 它们不会泄漏,而且非常有效.或者,您可以使用"所有者指针"跨范围边界传递所有权.这些是不常见的,必须明确使用; 它们允许对泄漏进行部分静态检查.

"哦,是吗?但是呢......

...如果我只是按照过去编写C++的方式编写代码?"

实际上,您可以忽略所有指导原则并编写泄漏的应用程序代码 - 它将一如既往地编译和运行(和泄漏).

但这不是"只是不要那样做"的情况,开发人员应该是善良的,并且要进行大量的自我控制; 编写不符合规范的代码并不简单,编写速度也不快,也不是表现更好.逐渐地,写入也会变得更加困难,因为您将面对符合代码提供和期望的"阻抗不匹配".

......如果我reintrepret_cast?还是做指针运算?还是其他这样的黑客?"

事实上,如果你把它放在心上,你就可以编写令人讨厌的代码,尽管对指南很好.但:

    你很少这样做(在代码中的位置,不一定是执行时间的分数)

    你只会故意这样做,而不是偶然.

    这样做将在符合指南的代码库中脱颖而出.

    这是一种代码,你可以用另一种语言绕过GC.

......图书馆发展?"

如果您是C++库开发人员,那么您确实编写了涉及原始指针的不安全代码,并且您需要仔细和负责任地编写代码 - 但这些代码是由专家编写的自包含代码(更重要的是,由专家审查).


所以,就像Bjarne说的那样:一般来说,没有动力收集垃圾,因为你们都要确保不要产生垃圾.GC正在成为C++的一个问题.

这并不是说当您想要使用自定义分配和取消分配策略时,GC对某些特定应用程序来说不是一个有趣的问题.对于那些您需要自定义分配和取消分配的人,而不是语言级别的GC.


@ user1863152:在这种情况下,自定义分配器将很有用。仍然不需要使用语言集成的GC ...
推荐阅读
oDavid_仔o_880
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有