我问这个问题,首先不是因为垃圾收集的优点。我问这个问题的主要原因是我知道Bjarne Stroustrup说过c++在某个时间点上会有一个垃圾收集器。
话虽如此,为什么还没有加入呢?c++已经有了一些垃圾收集器。这是那种“说起来容易做起来难”的事情吗?或者还有其他原因没有添加它(并且不会在c++ 11中添加)?
交叉链接:
c++的垃圾收集器
澄清一下,我理解c++最初创建时没有垃圾收集器的原因。我想知道为什么不能添加收集器。
我问这个问题,首先不是因为垃圾收集的优点。我问这个问题的主要原因是我知道Bjarne Stroustrup说过c++在某个时间点上会有一个垃圾收集器。
话虽如此,为什么还没有加入呢?c++已经有了一些垃圾收集器。这是那种“说起来容易做起来难”的事情吗?或者还有其他原因没有添加它(并且不会在c++ 11中添加)?
交叉链接:
c++的垃圾收集器
澄清一下,我理解c++最初创建时没有垃圾收集器的原因。我想知道为什么不能添加收集器。
当前回答
为了增加争论。
关于垃圾收集有一些已知的问题,了解它们有助于理解为什么c++中没有垃圾收集。
1. 性能?
第一个抱怨通常是关于性能,但大多数人并没有真正意识到他们在谈论什么。正如马丁·贝克特(Martin Beckett)所指出的,问题可能不是表现本身,而是表现的可预测性。
目前有两个GC家族被广泛部署:
标记和清扫类 引用计数类型
标记和清除更快(对整体性能的影响较小),但它患有“冻结世界”综合征:即当GC开始时,其他一切都停止,直到GC完成清理。如果您希望构建一个在几毫秒内响应的服务器……有些交易不会达到你的期望:)
The problem of Reference Counting is different: reference-counting adds overhead, especially in Multi-Threading environments because you need to have an atomic count. Furthermore there is the problem of reference cycles so you need a clever algorithm to detect those cycles and eliminate them (generally implement by a "freeze the world" too, though less frequent). In general, as of today, this kind (even though normally more responsive or rather, freezing less often) is slower than the Mark And Sweep.
I have seen a paper by Eiffel implementers that were trying to implement a Reference Counting Garbage Collector that would have a similar global performance to Mark And Sweep without the "Freeze The World" aspect. It required a separate thread for the GC (typical). The algorithm was a bit frightening (at the end) but the paper made a good job of introducing the concepts one at a time and showing the evolution of the algorithm from the "simple" version to the full-fledged one. Recommended reading if only I could put my hands back on the PDF file...
2. 资源获取初始化(RAII)
在c++中,将资源的所有权包装在对象中以确保它们被正确地释放是一种常见的习惯用法。它主要用于内存,因为我们没有垃圾回收,但它对许多其他情况也很有用:
锁(多线程,文件句柄,…) 连接(到数据库、另一台服务器……)
其思想是正确地控制对象的生命周期:
只要你需要,它就应该是活的 当你用完它的时候,它应该被杀死
GC的问题在于,如果它有助于前者,并最终保证以后……这个“终极”可能还不够。如果你释放一个锁,你真的希望它现在被释放,这样它就不会阻止任何进一步的调用!
带有GC的语言有两种解决方法:
当堆栈分配足够时不要使用GC:这通常是为了解决性能问题,但在我们的例子中,它确实有帮助,因为作用域定义了生命周期 使用构造…但它是显式(弱)RAII,而在c++中RAII是隐式的,因此用户不能在不知不觉中犯错误(通过省略using关键字)
3.智能指针
在c++中,智能指针通常是处理内存的灵丹妙药。我经常听到:我们根本不需要GC,因为我们有智能指针。
这是大错特错了。
智能指针确实有帮助:auto_ptr和unique_ptr使用RAII概念,确实非常有用。它们很简单,你可以很容易地自己写出来。
然而,当一个人需要共享所有权时,它变得更加困难:你可能在多个线程之间共享,并且在计数的处理上有一些微妙的问题。因此,很自然地使用shared_ptr。
这很棒,毕竟这就是Boost的用途,但它并不是万能的。事实上,shared_ptr的主要问题是它模拟了一个由引用计数实现的GC,但你需要自己实现周期检测…开始
当然有这个weak_ptr的东西,但不幸的是,我已经看到内存泄漏尽管使用shared_ptr,因为这些周期…当你在多线程环境中,它是非常难以检测的!
4. 解决方案是什么?
没有什么灵丹妙药,但一如既往,这绝对是可行的。在没有GC的情况下,需要明确所有权:
如果可能的话,最好在一个特定的时间拥有一个所有者 如果没有,请确保您的类图没有与所有权相关的任何循环,并通过weak_ptr的微妙应用来打破它们
所以,如果有一个GC…然而,这不是一个微不足道的问题。与此同时,我们只需要卷起袖子。
其他回答
简短的回答: 我们不知道如何高效地(在很少的时间和空间开销下)并且始终(在所有可能的情况下)正确地进行垃圾收集。
长一点的回答: 就像C一样,c++是一种系统语言;这意味着当您编写系统代码时,例如操作系统时,将使用它。换句话说,c++就像C一样,以尽可能好的性能作为主要目标。该语言标准不会增加任何可能阻碍性能目标的特性。
这暂停了这个问题:为什么垃圾收集会影响性能?主要原因是,当涉及到实现时,我们(计算机科学家)不知道如何在所有情况下以最小的开销进行垃圾收集。因此,c++编译器和运行时系统不可能一直有效地执行垃圾收集。另一方面,c++程序员应该了解他的设计/实现,他是决定如何最好地进行垃圾收集的最佳人选。
最后,如果控制(硬件、细节等)和性能(时间、空间、电源等)不是主要的限制,那么c++就不是合适的工具。其他语言可能会更好,并提供更多[隐藏的]运行时管理,以及必要的开销。
本来可以添加隐式垃圾收集,但它没有达到要求。可能不仅仅是因为实现的复杂性,还因为人们不能足够快地达成普遍共识。
引用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++和Java时,我们看到c++在设计时并没有考虑到隐式垃圾收集,而Java则是。
在C风格中使用任意指针这样的东西不仅不利于gc实现,而且还会破坏大量c++遗留代码的向后兼容性。
除此之外,c++是一种旨在作为独立可执行文件运行的语言,而不是具有复杂的运行时环境。
总之: 是的,在c++中添加垃圾收集是可能的,但是为了连续性,最好不要这样做。
什么类型?它应该针对嵌入式洗衣机控制器、手机、工作站或超级计算机进行优化吗? 它应该优先考虑gui响应还是服务器加载? 它应该使用大量内存还是大量CPU?
C/c++被用于太多不同的环境。 我怀疑像增强智能指针这样的东西对大多数用户来说就足够了
编辑:自动垃圾收集器并不是一个性能问题(你总是可以购买更多的服务器),而是一个可预测的性能问题。 不知道GC什么时候会起作用就像雇佣一个嗜睡症的飞行员,大多数时候他们是很棒的-但当你真的需要响应的时候!
为了增加争论。
关于垃圾收集有一些已知的问题,了解它们有助于理解为什么c++中没有垃圾收集。
1. 性能?
第一个抱怨通常是关于性能,但大多数人并没有真正意识到他们在谈论什么。正如马丁·贝克特(Martin Beckett)所指出的,问题可能不是表现本身,而是表现的可预测性。
目前有两个GC家族被广泛部署:
标记和清扫类 引用计数类型
标记和清除更快(对整体性能的影响较小),但它患有“冻结世界”综合征:即当GC开始时,其他一切都停止,直到GC完成清理。如果您希望构建一个在几毫秒内响应的服务器……有些交易不会达到你的期望:)
The problem of Reference Counting is different: reference-counting adds overhead, especially in Multi-Threading environments because you need to have an atomic count. Furthermore there is the problem of reference cycles so you need a clever algorithm to detect those cycles and eliminate them (generally implement by a "freeze the world" too, though less frequent). In general, as of today, this kind (even though normally more responsive or rather, freezing less often) is slower than the Mark And Sweep.
I have seen a paper by Eiffel implementers that were trying to implement a Reference Counting Garbage Collector that would have a similar global performance to Mark And Sweep without the "Freeze The World" aspect. It required a separate thread for the GC (typical). The algorithm was a bit frightening (at the end) but the paper made a good job of introducing the concepts one at a time and showing the evolution of the algorithm from the "simple" version to the full-fledged one. Recommended reading if only I could put my hands back on the PDF file...
2. 资源获取初始化(RAII)
在c++中,将资源的所有权包装在对象中以确保它们被正确地释放是一种常见的习惯用法。它主要用于内存,因为我们没有垃圾回收,但它对许多其他情况也很有用:
锁(多线程,文件句柄,…) 连接(到数据库、另一台服务器……)
其思想是正确地控制对象的生命周期:
只要你需要,它就应该是活的 当你用完它的时候,它应该被杀死
GC的问题在于,如果它有助于前者,并最终保证以后……这个“终极”可能还不够。如果你释放一个锁,你真的希望它现在被释放,这样它就不会阻止任何进一步的调用!
带有GC的语言有两种解决方法:
当堆栈分配足够时不要使用GC:这通常是为了解决性能问题,但在我们的例子中,它确实有帮助,因为作用域定义了生命周期 使用构造…但它是显式(弱)RAII,而在c++中RAII是隐式的,因此用户不能在不知不觉中犯错误(通过省略using关键字)
3.智能指针
在c++中,智能指针通常是处理内存的灵丹妙药。我经常听到:我们根本不需要GC,因为我们有智能指针。
这是大错特错了。
智能指针确实有帮助:auto_ptr和unique_ptr使用RAII概念,确实非常有用。它们很简单,你可以很容易地自己写出来。
然而,当一个人需要共享所有权时,它变得更加困难:你可能在多个线程之间共享,并且在计数的处理上有一些微妙的问题。因此,很自然地使用shared_ptr。
这很棒,毕竟这就是Boost的用途,但它并不是万能的。事实上,shared_ptr的主要问题是它模拟了一个由引用计数实现的GC,但你需要自己实现周期检测…开始
当然有这个weak_ptr的东西,但不幸的是,我已经看到内存泄漏尽管使用shared_ptr,因为这些周期…当你在多线程环境中,它是非常难以检测的!
4. 解决方案是什么?
没有什么灵丹妙药,但一如既往,这绝对是可行的。在没有GC的情况下,需要明确所有权:
如果可能的话,最好在一个特定的时间拥有一个所有者 如果没有,请确保您的类图没有与所有权相关的任何循环,并通过weak_ptr的微妙应用来打破它们
所以,如果有一个GC…然而,这不是一个微不足道的问题。与此同时,我们只需要卷起袖子。