我唯一接触过垃圾回收机制是在几台廉价的80年代家用电脑上,它会导致系统每隔一段时间卡顿几秒钟。我相信它现在已经得到改进,但你可以想象,那并没有给我留下很高的评价。
对于有经验的C++开发者,垃圾回收机制可以提供哪些优势呢?
public class MaximumItemFinder
{
String maxItemName = "";
int maxItemValue = -2147483647 - 1;
public void AddAnother(int itemValue, String itemName)
{
if (itemValue >= maxItemValue)
{
maxItemValue = itemValue;
maxItemName = itemName;
}
}
public String getMaxItemName() { return maxItemName; }
public int getMaxItemValue() { return maxItemValue; }
}
maxItemName = itemName;
这样的语句可能会生成两条指令:一个寄存器加载后跟着一个寄存器存储。 MaximumItemFinder
不知道调用者是否会保留传入字符串的任何引用,而调用者也无法知道 MaximumItemFinder
将保留对它们的引用多长时间。调用getMaxItemName
的调用者无法知道 MaximumItemFinder
和返回的字符串的原始供应商是否已放弃所有对它的引用。但是,由于代码可以像传递原始值一样传递字符串引用,因此这些事情都不重要。AddAnother
时不是线程安全的,但是任何对GetMaxItemName
的调用都保证返回一个有效的引用,可以是空字符串或已传递给AddAnother
的字符串之一。如果想确保最大项名称和其值之间的任何关系,则需要进行线程同步,但是即使没有线程同步,内存安全性也得到了保证。int
那样便宜地定义、赋值和传递。int
的赋值一样原子化 [即a=b
执行了对a
的原子读取和对b
的原子写入,尽管整个操作可能不是原子的]。一个关键要求是GC必须知道在赋值期间b
被读入的寄存器(如果执行赋值的线程在此期间被交换出去,b
被覆盖并且需要进行GC循环,那么该线程的寄存器可能是宇宙中唯一对b
的引用)。 - supercatb = c ;
,而另一个线程在同一时间执行 a = b ;
,其中 b 是两个线程共享的原始指针。此时,b 可能被线程 1 部分写入,被线程 2 完全读取,然后再被线程 1 最后部分写入(我不认为我们在标准、可移植的 C++ 中自动具有原子赋值指针)。这意味着线程 2 具有不正确的指针值。我的理解正确吗? - paercebalif(gcBusy)gcWait();
检查。如果假设GC具有这些功能,那么要求非切片指针分配相比之下就微不足道了。 - supercat处理循环引用等事项的完整GC在ref-counted shared_ptr
方面会有所升级。我在C++中也欢迎这种方式,但不是在语言层面上。
C++的美妙之一在于它不会强制进行垃圾回收。
我想纠正一个常见的误解:垃圾回收神话,认为它可以消除泄漏。根据我的经验,在与通过资源密集型主机应用程序使用垃圾回收等语言编写的代码进行调试时,最令人头痛的是逻辑泄漏。
谈论GC等主题时,有理论和实践之分。理论上,它很棒并且可以防止泄漏。然而,在理论层面上,每种语言都是美好的且无泄漏的,因为在理论上,每个人都会编写完全正确的代码,并测试每种可能出现问题的情况。
垃圾回收与不太理想的团队协作相结合,在我们的情况下导致了最糟糕、最难以调试的泄漏。
问题仍然与资源所有权有关。当涉及到持久对象时,您必须在这里做出明确的设计决策,而垃圾回收使人们很容易认为不需要这样做。
在开发人员没有始终进行交流和仔细审查彼此代码的情况下(在我的经验中非常普遍),在团队环境中给定某些资源R
,开发人员A
很容易存储对该资源的句柄。开发人员B
也是如此,可能以间接方式将R
添加到某个数据结构中。C
也是如此。在垃圾回收的系统中,这已经创建了3个R
的所有者。
因为开发人员A
最初创建了该资源并认为自己是其所有者,所以当用户表示不再需要使用它时,他记得释放对R
的引用。毕竟,如果他未能这样做,什么都不会发生,并且从测试中可以明显看出用户端移除逻辑未起作用。因此,他记得释放它,就像任何合理的开发人员一样。这触发了一个事件,B
处理它并且也记得释放对R
的引用。
C
忘记了。他不是团队中更强的开发人员之一:一个相对新的招募者,只在系统中工作了一年。或者他甚至不在团队中,只是一个受欢迎的第三方开发人员,为我们的产品编写插件,许多用户将其添加到软件中。使用垃圾回收时,这就是我们获得那些静默逻辑资源泄漏的时候。它们是最糟糕的类型:它们不一定会在软件的用户可见侧面表现为明显的错误,除了在运行程序的持续时间内,内存使用量只是继续上升,目的不明。试图用调试器缩小这些问题可能会像调试时间敏感的竞争条件一样有趣。C
将创建一个悬空指针。他可能会尝试在某个时候访问它,并导致软件崩溃。现在那是一个测试/用户可见的错误。 C
有点尴尬,并纠正了他的错误。在GC场景下,仅仅尝试找出系统在哪里泄漏可能是如此困难,以至于其中一些泄漏永远不会被纠正。这些不是valgrind
-类型的物理泄漏,可以很容易地检测到,并指向特定的代码行。C
创建了一个非常神秘的泄漏。他的代码可能会继续访问现在只是软件中一些不可见的实体的R
,此时与用户无关,但仍处于有效状态。随着C
的代码创建更多的泄漏,他正在创建更多的隐藏处理不相关资源的过程,软件不仅泄漏内存,而且每次都变得越来越慢。我也怀疑C++委员会是否会将全面的垃圾回收机制添加到标准中。
但是我认为,现代语言中添加/拥有垃圾回收的主要原因是,反对垃圾回收的好理由太少了。自80年代以来,内存管理和垃圾回收领域取得了几项重大进展,我相信甚至有一些垃圾回收策略可以给您提供软实时保障(例如,“在最坏情况下GC不会超过...”)。
weak_ptr
作为解决方法是退回到手动内存管理。free
、双重free
)。此外,垃圾回收还促进了其他形式的编程,例如通过解决上行funarg问题 (1970)。unique_ptr
-> 失败。你在作用域结束时使用 vcall 的例子是 boost::shared_ptr 特有的事情,根本不需要这样做。此外,GC 比 RAII 保留更多的垃圾。更不用说 GC 有一些非常严重的缺点,比如资源的非确定性销毁,无法与非 GC 内存/资源一起使用等等。此外,你的帖子是误导性的,因为它暗示了 RAII 用户面临悬空指针、内存泄漏和双重删除的问题,而实际上他们并没有。简而言之,你是错误的。 - Puppyunique_ptr
会导致悬空指针。引用计数泄漏。线程不安全的shared_ptr
可能会导致双重删除。 - J Dshared_ptr
不意味着或要求虚拟析构函数。使用类型擦除是用户的选择。你的内存轶事是一种逻辑谬误(奇怪的是,是个轶事)。显然,垃圾收集器总会有比在不可引用时立即释放它的系统中更多的垃圾。没有有意义的非线程安全的 shared_ptr
实现,unique_ptr
的接口不允许双重所有权,除非你真的很努力,而且说实话,引用循环从来没有出现过。 - PuppyBOOST_SP_DISABLE_THREADS
。“循环引用永远不会出现”。因为您正在做出牺牲,以适应糟糕的内存管理策略。在JVM和.NET上,循环引用是无处不在的。 - J D