"凡事都有定期,天下万务都有定时:生有时,死有时;"——《传道书》
对象生命周期是个很大的课题 ,本文仅是科普贴,简要地介绍了对象生命周期管理的概念,原理,方法。
对象
在程序事务中,涉及使用各种资源(内存,IO句柄,GDI等),在面向对象方法学里,我们将资源包装成对象,对象的 构造/销毁,对应资源的申请/释放。
生命周期
我们不能一直创建对象/资源,而失去销毁的机会(资源泄露),也不能在资源销毁后,继续访问(野指针,无效句柄),我们需要在合适的时机释放合适的对象。
对象生命周期管理分三类,
- 手动管理
- 半自动管理 —— 编译器检查,通过库实现的智能指针等
- 全自动管理 —— GC
一些例子:
-
C语言提供最基础的资源管理模型,
内存分配释放,malloc, free,
-
文件句柄分配释放
File* f = openFile() ... closeFile(f)
-
模块
习惯上,我们通过提供API,帮助用户在模块级别上进行管理module_hanler* h = module_init(...); h.operator(...) module_release(h)
手动管理麻烦,容易出错,调试复杂,但操作系统级别的开发,不可缺少。
-
c++的对象语义提供了RAII
-
通过构造函数,析构函数,能够完成作用域的自动化管理,
//CFile.h class CFile{ File* fh = NULL; public: CFile(char* path) { fh = openFile(path); ... } ~CFile() { closeFile(fh); } } //testFile.cpp void testFile() { CFile f("xxx.txt"); f.print(); }//这里自动释放
- c# 提供了 Dispose接口 和 using关键字,离开作用域的对象立即回收
RAII,一定程度上,减轻了程序员管理内存的负担,但它无法帮助解决,对象被引用,对象传递到作用域外的问题。
-
-
(编译器自动管理)智能指针
裸指针:离开作用域,不会自动销毁,需要主动调用 delete obj_ptr;
-
智能指针:
通过编译器的魔法,我们创造出智能指针(smart_ptr),既保留了作用域的安全性,也保留了指针灵活性
智能指针设计到以下的概念, 所有权转移: 当发生指针赋值的情况,原指针失效,如果再次使用,引发编译时错误(rust),运行时错误(c++)
对应c++的unique_ptr,rust对象(非实现copy trait的对象,进行所有权转移)-
引用计数
一个指针被多个对象引用,当所有对象都不再持有,对象自动销毁,引用计数有时被看作是一种即时回收的GC,
c++提供了, uniq_ptr, shart_ptr, weak_ptr, 对应以上的概念,但还面临线程安全问题
rust 提供了更多的智能指针类型,和检查机制,能够处理包括,所有权转移后不可再用, 线程间资源竞争之类的问题
-
垃圾回收来自于这样的想法,程序员无需关心对象如何回收,由运行时系统解决。
原理:
已创建对象 - 环境栈上的对象 - 所有可到达对象 = 垃圾对象标记-清除 算法:
- 所有对象初始标记 不可回收
- 资源使用到达阈值-> 翻转不可回收标记为可回收标记,
- 栈对象加入可访问对象集,
- 个可访问对象的引用成员,加入可访问对象集,并标记不可回收,递归至所有可访问对象上色,
- 遍历全部对象,清除所有标记回收的对象
实际的垃圾回收算法极其复杂,至今,没有一个完美的并发算法,使得在GC时,系统不暂停(stop the world)
-
其它
- 整页分配
对于一次事务,分配足够内存(或是动态增长), 对象只创建,无需回收,事务完成,整页回收,这样的好处是分配极快,但需要清楚事务的边界,不可跨事务使用。
-
对象池
对象池 省去对象反复创建,销毁的成本
对象需要提供,无参构造函数,init(...), reset()规范,对象池可通过栈管理。template<typename OBJECT> class object_pool{ OBJECT* get_object(...) { auto object = pool.pop() object.init(...) ... } void release_object(OBJECT object) { object.reset() pool.push(object) } ... }
如果你在实践中,还有什么对象管理的技巧,请告诉我 (lxf0525@gmail.com)