RAII和垃圾收集(下)

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

上回说到,RAII 与现有的 GC 环境互不相容,也提到了问题的症结在于对析构函数的调用。这并非仅仅是一个令人遗憾的巧合,仔细想想不难发现,在这个矛盾背后,实际上是两者在“如何看待一个对象”这一问题上的分歧。

前面说过,RAII 的核心内容是把资源托管给对象,并保证资源在对象生命周期内始终有效。这样,我们实际上把管理资源的任务转化成了管理对象的任务,你拥有对象就等于拥有资源,对象存在则资源必定存在,反之亦然。RAII 的全部威力均源于此。这种做法的出发点,用 Bjarne Stroustrup 的话来说,是“use an object to represent a resource”(引自本文上篇开头所引用的代码中的注释)。换句话说,在 RAII 的眼中,对象代表了资源,它的行为和状态应该与资源的行为和状态完全一致,所以资源的申请和释放也就自然应该和对象的构造与析构对应起来。对于大多数资源,程序员有权而且需要控制它何时释放,那么很自然的,对于管理这些资源的对象,程序员也就应该有权而且需要控制它何时析构。一言以蔽之,这要求程序员有权控制对象的生命周期。

然而现有的 GC 环境并不满足上述条件。在本文的上篇中介绍过,对于 GC 来说,一个对象的生命周期有多长程序员是无权干涉的,任给对象 A,它是否存活必须严格地按照“是否存在一条从根出发的指针链能够到达 A”来判定。在 GC 的眼中,对象只是一段被分配的内存,而历史已经证明,决定内存何时释放这个任务决不能交给爱犯错的程序员,应该由专门的收集器完成,以确保每个内存回收动作都是安全的。什么?一个对象代表一份资源?哈!我 GC 大人的眼中只有内存,没有什么资源!

啧啧,两者眼中对象的角色差异如此之大,难怪会水火不容了。不过,这个矛盾真的没有调和的可能吗?倒也不见得。RAII 看重对象的语义,要让对象代表资源,因此需要程序员能够控制对象的生命周期,而 GC 眼中只有内存,反复强调只有让收集器来回收内存才能保证安全,这活不能让程序员来干。因此,要同时满足这两者的要求,对象的生命周期和必须和内存的释放脱钩。这不是什么新鲜想法。事实上,按照 C++ 标准的定义,一个对象的生命周期始于构造函数,终于析构函数,它本来就不是由内存分配释放来决定的,而 placement new 更是把这一点表现的极为充分,分配内存、构造对象、析构对象、释放内存四个操作清清楚楚、各自独立。至此,我们的解决方案已经呼之欲出了。一方面,我们要把释放内存的责任移交给 GC,同时又要允许程序员控制对象生命周期,那么,正确的做法就是 ……

在现有 GC 机制的基础上,允许程序员显式地析构对象,同时,若程序试图访问已经析构的对象,将抛出异常。

这初看起来非常荒谬(我已经可以看到台下飞来的无数鸡蛋和番茄 …… 哎哎?!你们真砸啊?),但其实并非如此。首先,内存的分配和释放由 GC 机制管理,凡是指针可到达的内存总是有效的,因此我们拥有同样的安全性保证 —— 没有悬挂引用和内存泄漏,所以,我们可以放心大胆地在模块之间共享对象,不必操心悬挂引用的问题,也不必担心内存管理的细节“弄脏”模块之间的接口;其次,由于允许显式析构对象,程序员能够控制对象的生命周期,因此我们可以继续应用 RAII “用对象代表资源”的语义并享受它带来的便利;最后,试图访问已经析构的对象将会抛出异常,堵上了无定义行为的口子。一句话,现在我们可以把对象放入支持 GC 的堆里而不必改变它的语义了。

瞧!两边不是合作得很好么?

必须承认的是,要实现这个方案,实际上是非常简单的,但是就我所知,目前并没有任何一个 GC 环境支持这个做法。这是因为大多数支持 GC 的语言并没有类似 C++ 中的析构函数这样的语言特征,换句话说,压根没有应用 RAII 的能力,而 C++ 本身又并不支持 GC。嗯嗯,面对这种情况,我决定效法先贤 —— 自己实现一个用于 C++ 的垃圾收集库,用智能指针来实现对指针访问的控制,并提供显式析构的接口。

最后,我来试着回答某些可能出现的质疑:

问:显式析构和显式调用 close 有什么区别?不一样要程序员自己动手吗?
答:有区别。请参阅本文上篇中,关于 RAII 相对于显式释放资源方案的优势。

问:你允许程序员析构对象,那么可能出现指向已析构对象的指针,这和悬挂指针不是一样么?
答:不一样。如果你试图访问已析构对象,只会抛出一个异常(当然,这得通过智能指针来实现),而访问悬挂指针 …… 不用我多说吧?

问:(续上)这样一来,通过指针访问对象可能抛出异常,这是原来没有的。
答:这种说法不准确。在现有的 GC 机制下,对象占有的资源需要调用(例如)close 成员函数显式释放,而在调用了 close 之后,再试图访问这个对象一样会抛出异常,指明使用这个对象所需的资源已经被释放。而且,对于现有的方案,每种对象释放资源的函数可能都不同,标识“资源已释放”的异常可能都不同,但如果使用显式析构的方案,释放资源的手段是一致的(析构对象),标识“资源无效”的异常也是一致的(“对象已析构”),这大大减轻了程序员的簿记负担。

问:(再续)但是你多一个检查啊?每个指针访问都需要检查对象是否已析构,效率太低。
答:这种说法也不准确。采用显式调用 close 释放资源的方案,在该对象内部,每个完成实质性工作(因此需要访问对象所需的资源)的成员函数同样必须检查资源是否有效,也是一个检查。而且,这样的检查程序员需要为每个类分别撰写,而采用上述显式析构的方案,只有智能指针需要做这个检查。

问:(再续)你上述两个问题的回答多少有点偏颇吧?并不是对象的每个成员函数都需要访问资源,你上述两个辩解对于不需要访问资源的成员函数是不成立的。而且,也并不是所有的对象都需要管理资源,如果我想在支持 GC 的堆里放一个 int 怎么办?这样,“可能会抛异常”和“多一个检查”无疑是两个很大的缺点了吧?
答:嗯嗯,这可能是最有力的质疑了,我的回答是这样的:我会另外提供一种不支持显式析构的智能指针,它的使用就像传统 GC 环境下的指针一样,也就没有“可能会抛异常”和“多一个检查”的问题。换句话说,如果你的对象不管理资源,或者你一定要在资源释放之后还能继续访问对象,用它你可以退回到传统的 GC 方案去。

问:我就是不喜欢你这套想法!有意见吗?
答: …… 没意见。如果你确实无法接受“显式析构”的想法,你也可以只使用上面提到的另一种智能指针 —— 嗯嗯,除了“显式析构”之外,我对于如何在 C++ 中具体实现垃圾收集机制也有很多其他的想法,除了“显式析构”之外,我的收集器应该还会有其他方面的优点。当然,这是其他文章的内容了。

好,大致就是这样,希望大家多提意见。如果哪位对实现垃圾收集器这个具体工作有兴趣,请来这边坐坐:
http://www.allaboutprogram.com/bb/viewtopic.php?t=1520

python GC RAII GIL RAII python内存管理 __del__ GIL RAII RAII 是一个资源管理工具,约束在代码执行走出特定作用域之后,不管是正常流程,还是异常流程,都不会漏掉资源的释放,可以极大简化代码编写(不用每个分支都增加资源释放逻辑)资源管理。多数情况下,都应该尽早释放资源,而不应该依赖垃圾收集不可控的生命周期,比如文件描述符、数据库连接。RAII可以严格绑定资源的有效期与变量的生命周... 阅读详情

相关推荐

现代C++3-堆、栈、RAIIC++里该如何管理资源?

今天我们就正式开启了 C++ 的学习之旅,作为第一讲,我想先带你把地基打牢。我们来学习一下内存管理的基本概念,大致的学习路径是:先讲堆栈,然后讨论 C++ 的特色功能 RAII。掌握这些概念,是能够熟练运用 C++ 的基础。

qq_53280238的博客 927

C++惯用法之消除垃圾收集器-资源获取即初始化方法(RAII)

C++惯用法之消除垃圾收集器-资源获取即初始化方法导语RAII的发明:新希望“使用垃圾收集器,flyboy!”所有权:RAII觉醒只有在最后,你才意识到RAII的真正力量。 导语 在C语言中,有三种类型的内存分配:静态、自动动态。静态变量是嵌入在源文件中的常数,因为它们有已知的大小并且从不改变,所以它们并不那么有趣。自动分配可以被认为是堆栈分配——当一个词法块进入时分配空间,当该块退出时释放空间。它最重要的特征与此直接相关。在C99之前,自动分配的变量需要在编译时知道它们的大小。这意味着任何字符串、列表、

希望我的博客,能帮上你解决学习中工作中所遇到的问题 472

C++惯用法之RAII-消除垃圾收集器】-资源自动管理方法

doSomething() 函数中创建了一个 FileWriter 对象,并调用其 write() 方法,该方法向文件中写入一条消息。RAII 技术用于管理对象的生命期,当对象生命期结束,它所占据的资源也应该被释放。这种技术通常与类的构造函数析构函数相结合使用,从而实现了资源的自动管理。通过 RAII 技术,我们可以避免在代码中手动管理资源,从而减少错误内存泄漏的风险。总的来说,RAII 技术是 C++ 中非常实用的资源管理技术,它可以大幅度简化程序员的编程工作,并且提高程序的安全性稳定性。

CoderHH的博客 216

RAII垃圾收集GC

RAII垃圾收集GC,今天无意中看到了RAII,就找到了这篇文章,写的很好,值得一读!

愤怒的小狐狸----博客专栏 2485

RAII垃圾收集

Author:Elminsterhttp://blog.csdn.net/Elminster/先来看一小段代码,它取自 Bjarne Stroustrup 的演讲“Speaking C++ as a Native”:// use an object to represent a resource ("resource acquisition is initialization")cla

zheng80037的专栏 649

c++的内存管理(raii->shart_ptr->垃圾回收)

1. 参考: 什么情况下 C++ 需要垃圾处理机制? https://www.zhihu.com/question/308638046 这个链接里,很多人的观点不同,但都值得去理解学习。 2. c++11里share_ptr的用法相关(循环引用的避免在大项目里难以保证----这个在《什么情况下 C++ 需要垃圾处理机制?》里,大家已经分析过了。但还是有用的。) 参考: (1) 浅谈shared_ptr及shared_ptr涉及到的循环引用问题 https://blog.cs..

wrt_345177767的博客 497

C++11学习笔记7---资源回收RAII

资源回收包括:内存、线程、文件描述符等等 内存---shared_ptr 其他资源---RAII原则编程

山鸡哥的专栏 1011

RAII垃圾收集(上)

先来看一小段代码,它取自 Bjarne Stroustrup 的演讲“Speaking C++ as a Native”:// use an object to represent a resource ("resource acquisition is initialization")class File_handle { // belongs in some support libra

Elminster的专栏 2964

GCRAII+refcounting

 RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。   RAII 的一般做法是这样的:在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处: ...

qiuyumin430的博客 436

C++11 16-垃圾收集 Garbage Collection

智能指针可以与自定义删除器一起使用,允许为资源清理提供特定的逻辑。

wesigj的博客 2204

资源管理方案——RAII

Resource Acquisition Is Initialization   RAIIC++所特有的资源管理方式。有少量其他语言也采用RAII,但是C++是唯一一个依赖RAII来做资源管理的。RAII依托栈析构函数,来对所有的资源——包括堆内存在内进行管理。对于RAII的使用,使得C++不需要类似于Java那样的垃圾收集方法,也能有效的管理内存。RAII的存在,也是垃圾收集虽然理论上可以在C++中使用,但是从没有流行过的主要原因。   RAII有些比较成熟的智能指针代表,如:std::auto_p

千祠的博客 269

C++11 GC Interface:从入门到精通

C++11的前身是C++0x,其标准化历程跨越近十年。2003年,C++03发布,ISO委员会启动C++0x项目,原计划200X年完成。2005年提出“Concepts”(概念)特性,但因复杂度被推迟至C++20。2007年草案初稿完成,因特性过多首次延期。2010年特性冻结,提交最终委员会草案(FDIS)。2011年8月12日,ISO正式批准为ISO/IEC 14882:2011,同年9月标准发布,终结了C++98/03时代。

码事漫谈 769

JavaC++的对照

这是一个Java语言C++语言之间的比较. 目录   [隐藏]  1 设计目标2 语言特性 2.1 语法2.2 语义2.3 资源管理2.4 库2.5 运行时2.6 模板 vs. 泛型2.7 杂项 3 性能4 所有权控制5 参考文献6 外部链接 设计目标[编辑] C++Java语言之间的不同可以追溯到它们各自的传统,它们有着不同的设计目标。 C++ 被设计成主要用在系统性应

JAVAweb学习 570

d的自动无垃集代码.

d的自动@nogc

个人进步之路 154

智能指针及RAII

智能指针及RAII 问题 C++中最令人头疼的问题是强迫程序员对申请的资源(文件,内存等)进行管理,一不小心就会出现泄露(忘记对申请的资源进行释放)的问题。 // C++ auto ptr = new std::vector<int>(); //使用了垃圾回收技术,不在需要人为管理,相关的虚拟机会自动释放不需要使用的资源。 // Java ArrayList<int> list = new ArrayList<int>(); # Python lst = list(

森森猪的博客 298
上一篇: RAII和垃圾收集(上)
Elminster
博客等级 码龄25年 11粉丝 13原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值