我一直听到人们抱怨C++没有垃圾收集.我还听说C++标准委员会正在考虑将其添加到该语言中.我担心我只是没有看到它的意义...使用智能指针的RAII消除了它的需要,对吧?
我对垃圾收集的唯一经验是在几台便宜的80年代家用电脑上,这意味着系统每隔一段时间就会冻结几秒钟.我确信它从那时起已有所改善,但正如你可以猜到的那样,这并没有让我对此持高度评价.
垃圾收集为经验丰富的C++开发人员提供了哪些优势?
我很抱歉.认真.
C++有RAII,我总是抱怨在Garbage Collected语言中找不到RAII(或阉割的RAII).
另一个工具.
Matt J在他的帖子中写得非常正确(C++中的垃圾收集 - 为什么?):我们不需要C++功能,因为大多数都可以用C编码,我们不需要C功能,因为大多数功能都可以在汇编等编码.C++必须发展.
作为开发人员:我不关心GC.我尝试了RAII和GC,我发现RAII非常优越.正如Greg Rogers在他的文章(C++中的垃圾收集 - 为什么?)中所说的那样,内存泄漏并不是那么可怕(至少在C++中,如果真的使用C++,它们很少见),以证明GC而不是RAII.GC具有非确定性的释放/终止,并且只是编写一个不关心特定内存选择的代码的方法.
最后一句很重要:编写"juste不关心"的代码非常重要.以同样的方式在C++ RAII中我们不关心资源释放,因为RAII为我们做了,或者为了对象初始化,因为构造函数为我们做了,有时很重要的是只需编写代码而不关心谁是内存的所有者,以及我们需要什么样的指针(共享,弱,等等)或这段代码.在C++中似乎需要GC.(即使我个人没有看到它)
有时,在应用程序中,您有"浮动数据".想象一下树状结构的数据,但没有人真的是数据的"拥有者"(没有人真正关心它何时会被破坏).多个对象可以使用它,然后丢弃它.当没有人再使用它时,你希望它被释放.
C++方法使用智能指针.我想到了boost :: shared_ptr.因此,每个数据都由其自己的共享指针拥有.凉.问题是当每条数据都可以引用另一条数据时.您不能使用共享指针,因为它们使用的是引用计数器,它不支持循环引用(A指向B,B指向A).所以你必须知道很多关于在哪里使用弱指针(boost :: weak_ptr),以及何时使用共享指针.
使用GC,您只需使用树结构数据.
缺点是,你必须不在乎当 "流动数据"真的会被摧毁.只有它会被摧毁.
所以最后,如果做得恰当,并且与C++的当前习惯用法兼容,GC将成为C++的另一个好工具.
C++是一种多范式语言:添加GC可能会让一些C++粉丝因为叛国而哭泣,但最后,这可能是一个好主意,我猜C++标准委员会不会让这种主要功能打破语言,所以我们可以信任他们做出必要的工作来启用一个不会干扰C++的正确的C++ GC:像在C++中一样,如果你不需要一个功能,不要使用它,它将花费你没有.
简短的回答是垃圾收集原则上与使用智能指针的RAII非常相似.如果您分配的每一块内存都位于一个对象中,并且该对象仅由智能指针引用,那么您有一些接近垃圾收集的东西(可能更好).优势来自于不必如此明智地确定每个对象的范围和智能指针,并让运行时为您完成工作.
这个问题似乎类似于"C++必须为有经验的装配开发人员提供什么?指令和子程序消除了对它的需要,对吧?"
随着像valgrind这样的优秀记忆检查器的出现,我认为垃圾收集并没有太多用作安全网"万一"我们忘了解除某些东西 - 特别是因为它对管理更通用的资源案例没有多大帮助除了记忆(虽然这些不常见).此外,在我看到的代码中,显式分配和释放内存(即使使用智能指针)也是相当罕见的,因为容器通常更简单,更好.
但垃圾收集可能会提供潜在的性能优势,特别是如果很多短期对象正在堆分配.GC还可能为新创建的对象提供更好的引用局部性(与堆栈中的对象相当).
C++中GC支持的激励因素似乎是lambda编程,匿名函数等.事实证明,lambda库可以在不关心清理的情况下分配内存.普通开发人员的好处是更简单,更可靠,更快速地编译lambda库.
GC还有助于模拟无限内存; 您需要删除POD的唯一原因是您需要回收内存.如果您有GC或无限内存,则无需再删除POD.
该委员会没有添加垃圾收集,他们正在添加一些功能,以便更安全地实现垃圾收集.只有时间才能证明它们对未来的编译器是否真的有任何影响.具体实现可能有很大差异,但最有可能涉及基于可达性的收集,这可能涉及轻微挂起,具体取决于它是如何完成的.
但有一件事是,没有符合标准的垃圾收集器能够调用析构函数 - 只能静默地重用丢失的内存.
我不明白人们如何争辩RAII取代GC,或者是非常优越的.有很多案例由gc处理,RAII根本无法处理.他们是不同的野兽.
首先,RAII不是防弹:它可以解决一些在C++中普遍存在的常见故障,但在很多情况下RAII根本没有帮助; 异步事件(如UNIX下的信号)很脆弱.从根本上说,RAII依赖于范围界定:当变量超出范围时,它会自动释放(假设析构函数当然正确实现).
这是一个简单的例子,auto_ptr或RAII都不能帮助你:
#include#include #include #include #include using namespace std; volatile sig_atomic_t got_sigint = 0; class A { public: A() { printf("ctor\n"); }; ~A() { printf("dtor\n"); }; }; void catch_sigint (int sig) { got_sigint = 1; } /* Emulate expensive computation */ void do_something() { sleep(3); } void handle_sigint() { printf("Caught SIGINT\n"); exit(EXIT_FAILURE); } int main (void) { A a; auto_ptr aa(new A); signal(SIGINT, catch_sigint); while (1) { if (got_sigint == 0) { do_something(); } else { handle_sigint(); return -1; } } }
A的析构函数永远不会被调用.当然,这是一个人为的,有点人为的例子,但实际上也会发生类似的情况; 例如,当你的代码被处理SIGINT的另一个代码调用时,你完全没有控制权(具体的例子:matlab中的mex扩展).这也是为什么最终在python中不能保证执行某些东西的原因.在这种情况下,Gc可以帮助您.
其他成语不能很好地解决这个问题:在任何非平凡的程序中,你都需要有状态的对象(我在这里广泛使用对象这个词,它可以是语言允许的任何结构); 如果你需要在一个函数之外控制状态,那么用RAII就不能轻易做到这一点(这就是RAII对异步编程没有帮助的原因).OTOH,gc可以看到进程的整个内存,也就是它知道它分配的所有对象,并且可以异步清理.
使用gc也可以快得多,原因相同:如果你需要分配/解除分配许多对象(特别是小对象),gc将远远超过RAII,除非你编写自定义分配器,因为gc可以分配/一次通过清理许多物体.一些众所周知的C++项目使用gc,即使性能很重要(例如参见Tim Sweenie关于在虚幻竞技场中使用gc:http://lambda-the-ultimate.org/node/1277).GC以延迟为代价基本上增加了吞吐量.
当然,有些情况下RAII优于gc; 特别是,gc概念主要关注的是内存,而这并不是唯一的资源.像文件等等......可以用RAII很好地处理.没有像python或ruby这样的内存处理的语言确实有像RAII那样的情况,BTW(在python中使用语句).当您需要控制何时释放资源时,RAII非常有用,例如,文件或锁通常就是这种情况.
无需在经验较少的同事代码中追查资源泄漏.
垃圾收集允许推迟关于谁拥有对象的决定.
C++使用值语义,因此对于RAII,实际上,当超出范围时会重新收集对象.这有时被称为"立即GC".
当您的程序开始使用引用语义(通过智能指针等...)时,语言不再支持您,您将留下智能指针库的智慧.
关于GC的棘手问题在于决定何时不再需要某个对象.
假设由于C++没有将垃圾收集整合到语言中,因此不能在C++期间使用垃圾收集,这是一个常见错误.这是无稽之谈.我知道精英C++程序员使用Boehm收集器当然是他们的工作.
GC的一个属性在某些情况下可能非常重要.指针的赋值在大多数平台上自然是原子的,而创建线程安全的引用计数("智能")指针非常困难并且引入了显着的同步开销.因此,智能指针经常在多核架构上被告知"不能很好地扩展".
垃圾收集使RCU无锁同步更容易正确有效地实现.