为什么在RAII可用时收集垃圾?

我听说C ++ 14在C ++标准库本身中引入了一个垃圾收集器。 这个function的基本原理是什么? 这不是RAI存在于C ++中的原因吗?

  • 标准库垃圾收集器的存在将如何影响RAII语义?
  • 对我(程序员)或者我编写C ++程序的方式有什么影响?

垃圾收集和RAII在不同的环境中是有用的。 GC的存在不应该影响您对RAII的使用。 由于RAII是众所周知的,我举了两个例子,GC是方便的。


垃圾收集对于实现无锁数据结构将有很大的帮助。

事实certificate,确定性内存释放在无锁数据结构中是相当重要的问题。 (来自Andre- Alexandrescu的无锁数据结构 )

基本上问题是你必须确保在线程正在读取时不会释放内存。 这就是GC变得方便的地方:它可以查看线程,只有在安全的情况下才能进行重新分配。 请阅读文章的细节。

在这里要明确:这并不意味着整个世界应该像爪哇一样被垃圾收集; 只有相关的数据应该被垃圾收集准确。


在他的一个演讲中, Bjarne Stroustrup也给出了一个很好的,GC有用的例子。 想象一下用C / C ++编写的应用程序,大小为10M SLOC。 该应用程序工作相当好(相当无缺陷),但它泄漏。 你既没有资源(工时)也没有function知识来解决这个问题。 源代码是一个有点凌乱的遗留代码。 你是做什么? 我同意这也许是最简单和最便宜的方法来扫描GC下的问题。


正如sasha.sochka指出的那样 , 垃圾收集器将是可选的

我个人担心的是,人们会开始使用GC,就像在Java中使用GC一样,会写出草率的代码和垃圾收集所有内容。 (我的印象是shared_ptr已经成为默认的'去',即使在unique_ptr或hell,堆栈分配的情况下)。

我同意@DeadMG在当前的C ++标准中没有GC,但是我想从B. Stroustrup中添加以下引用:

当(不是)自动垃圾收集成为C ++的一部分时,它将是可选的

所以Bjarne肯定会在未来join。 至lessEWG(演进工作组)的主席和最重要的委员之一(更重要的是语言创造者)想要补充它。

除非他改变了自己的观点,否则我们可以期待它在未来得到补充和实施。

有一些algorithm复杂/低效/不可能在没有GC的情况下编写。 我怀疑这是GC在C ++中的主要卖点,并且永远也看不到它被用作通用分配器。

为什么不是通用的分配器?

首先,我们有RAII,大多数(包括我)似乎都认为这是资源pipe理的一种优越方法。 我们喜欢决定论,因为它使编写健壮,无泄漏的代码变得简单很多,并且使得性能可以预测。

其次,你需要放置一些非C ++类的限制,如何使用内存。 例如,你至less需要一个可达到的,没有模糊的指针。 混乱的指针,在常见的树形容器库(使用颜色标志保证低alignment的低位)中很受欢迎,GC不能识别。

与此相关的是,如果您支持任意数量的混淆指针,那么使现代GC可用的东西将非常难以应用于C ++。 代码碎片整理GC是非常酷的,因为分配是非常便宜的(本质上只是增加一个指针),最终你的分配被压缩成更小的局部性改进。 要做到这一点,物体需要是可移动的。

要使对象安全地移动,GC需要能够更新所有的指针。 它将无法find混淆的。 这可能是容纳,但不会很漂亮(可能是一个gc_pintypes或类似,使用像当前std::lock_guard ,这是用于每当你需要一个原始指针)。 可用性将在门外。

如果不把东西变成可移动的,那么GC就会比其他地方使用的GC慢得多,可扩展性也更差。

可用性原因(资源pipe理)和效率原因(快速,可移动的分配),GC还有什么好处? 当然不是通用的。 input无锁algorithm。

为什么无locking?

无锁algorithm的工作原理是使争用中的操作暂时与数据结构“不同步”,并在稍后的步骤中检测/纠正。 其中一个影响是,在争用之下,内存被删除之后可能会被访问​​。 例如,如果您有多个线程竞争从LIFO中popup一个节点,则可能是一个线程在另一个线程意识到节点已被占用之前popup并删除该节点:

线程A:

  • 获取指向根节点的指针。
  • 获取从根节点的下一个节点的指针。
  • 暂停

线程B:

  • 获取指向根节点的指针。
  • 暂停

线程A:

  • stream行节点。 (如果根节点指针读取后没有改变,则用下一个节点指针replace根节点指针。)
  • 删除节点。
  • 暂停

线程B:

  • 从指向根节点的指针获取指向下一个节点的指针,该指针现在“不同步”,刚刚被删除,所以我们崩溃了。

使用GC可以避免从未提交的内存中读取数据的可能性,因为当线程B引用它时,节点永远不会被删除。 有一些方法可以解决这个问题,例如危险指针或在Windows上捕获SEHexception,但这些可能会严重影响性能。 GC往往是这里最理想的解决scheme。

没有,因为没有一个。 C ++曾经用于GC的唯一特性是在C ++ 11中引入的,它们只是标记内存,不需要收集器。 在C ++ 14中也不会存在。

collections家可以通过委员会没有办法,是我的意见。

迄今为止的答案都没有涉及向语言添加垃圾收集的最重要的好处:在没有语言支持的垃圾收集的情况下,几乎不可能保证在引用它的时候不会有任何对象被销毁。 更糟糕的是,如果发生这样的事情,几乎不可能保证稍后使用该引用的尝试不会最终操纵一些其他随机对象。

虽然有很多种类的物体的寿命可以比由垃圾收集器好得多,但是使用GC来pipe理几乎所有的物体, 包括那些由RAII控制寿命的物体,都是非常有价值的。 一个对象的析构函数应该杀死这个对象并使其无效,但是为GC留下尸体。 因此,任何对物体的提及都将成为对尸体的参考,并将保持一直到它(参考)完全不存在为止。 只有当所有对尸体的提及已经不复存在的时候,尸体才会这样做。

尽pipe没有固有的语言支持,实现垃圾收集器的方法也是如此,但是这样的实现或者要求在任何时候引用被创build或者销毁的时候通知GC(增加相当大的麻烦和开销),或者冒着引用GC不知道的风险关于可能存在的一个对象,否则无关。 编译器对GC的支持消除了这两个问题。

GC具有以下优点:

  1. 它可以处理循环引用,无需程序员的帮助(使用RAII风格,您必须使用weak_ptr来打破圆圈)。 所以如果使用不当,RAII风格的应用程序仍然可能“泄漏”。
  2. 创build/销毁大量的shared_ptr到给定的对象可能是昂贵的,因为refcount递增/递减是primefaces操作。 在multithreading应用程序中,包含refcounts的内存位置将是“热”的地方,给内存子系统带来很大的压力。 GC不容易出现这个问题,因为它使用可达集而不是refcounts。

我不是说GC是最好的/最好的select。 我只是说它有不同的特点。 在某些情况下,这可能是一个优势。

定义:

RCB GC:基于引用计数的GC。

MSB GC:基于标记扫描的GC。

快速回答:

MSB GC应该添加到C ++标准中,因为在某些情况下它比RCB GC更方便。

两个说明性例子:

考虑一个全局缓冲区,其初始大小很小,任何线程都可以dynamic扩大其大小,并保持其他线程可访问的旧内容。

实施1(MSB GC版):

 int* g_buf = 0; size_t g_current_buf_size = 1024; void InitializeGlobalBuffer() { g_buf = gcnew int[g_current_buf_size]; } int GetValueFromGlobalBuffer(size_t index) { return g_buf[index]; } void EnlargeGlobalBufferSize(size_t new_size) { if (new_size > g_current_buf_size) { auto tmp_buf = gcnew int[new_size]; memcpy(tmp_buf, g_buf, g_current_buf_size * sizeof(int)); std::swap(tmp_buf, g_buf); } } 

实施2(RCB GC版):

 std::shared_ptr<int> g_buf; size_t g_current_buf_size = 1024; std::shared_ptr<int> NewBuffer(size_t size) { return std::shared_ptr<int>(new int[size], []( int *p ) { delete[] p; }); } void InitializeGlobalBuffer() { g_buf = NewBuffer(g_current_buf_size); } int GetValueFromGlobalBuffer(size_t index) { return g_buf[index]; } void EnlargeGlobalBufferSize(size_t new_size) { if (new_size > g_current_buf_size) { auto tmp_buf = NewBuffer(new_size); memcpy(tmp_buf, g_buf, g_current_buf_size * sizeof(int)); std::swap(tmp_buf, g_buf); // // Now tmp_buf owns the old g_buf, when tmp_buf is destructed, // the old g_buf will also be deleted. // } } 

请注意:

调用std::swap(tmp_buf, g_buf);tmp_buf拥有旧的g_buf 。 当tmp_buf被破坏时,旧的g_buf也将被删除。

如果另一个线程正在调用GetValueFromGlobalBuffer(index); 从旧的g_buf获取值,那么会出现赛车危险!

所以,虽然实现2看起来像实现1一样优雅,但它不起作用!

如果要使实现2正确工作,就必须增加某种锁机制; 那么它不仅会比实现1更慢,而且更不雅观。

结论:

作为可选function,将MSB GC转换为C ++标准是一件好事。