1. 资源管理问题背景
class Investment { ... }; // root class of hierarchy of// investment types
Investment* createInvestment(); // return ptr to dynamically allocated// object in the Investment hierarchy;// the caller must delete it// (parameters omitted for simplicity)
void f()
{Investment *pInv = createInvestment(); // call factory function... // use pInvdelete pInv; // release object
}
- 在使用如
createInvestment
这类返回动态分配对象指针的函数时,若仅依靠手动在合适位置(如函数末尾)调用delete
来释放资源,存在诸多导致资源泄漏的风险。例如函数中间出现提前的return
语句、循环因continue
或goto
提前退出、语句抛出异常等情况,都可能使delete
语句无法执行,进而不仅泄漏对象内存,还包括对象持有的其他资源。
2. 使用对象管理资源的理念(RAII)
- 核心观念:将资源放入一个对象内部,利用 C++ 自动调用析构函数的机制确保资源被释放,此即 Resource Acquisition Is Initialization(RAII)理念。获取资源后应立即移交给资源管理对象,常见方式是用获取的资源初始化资源管理对象,有时也可通过赋值操作移交资源,但重点是获取资源同时就将其交给资源管理对象。当对象离开其活动范围被销毁时,析构函数自动执行,从而保证资源能正确释放(除非释放资源时抛出异常,不过相关问题可参考 Item 8)。
3. 智能指针相关介绍
auto_ptr
: - 是一种智能指针,其析构函数会自动在指向的对象上调用
delete
。例如std::auto_ptr<Investment> pInv(createInvestment());
这种使用方式,能自动管理资源释放。 - 但
auto_ptr
有特殊的拷贝行为,拷贝(通过拷贝构造函数或拷贝赋值运算符)时会将原指针置为空,拷贝的指针获得资源唯一所有权,所以不能让多个auto_ptr
指向同一个对象,否则会导致对象被多次删除,引发程序的未定义行为。并且由于其非 “正常” 的拷贝行为,不适用于 STL 容器等要求内含物有常规拷贝行为的场景。
tr1::shared_ptr
(参考 Item 54): - 属于引用计数智能指针(RCSP),能跟踪指向特定资源的对象数量,当没有任何对象指向该资源时自动删除资源,行为类似垃圾收集,但不能解决循环引用问题。
- 拷贝
tr1::shared_ptr
的行为符合常规预期,例如多个shared_ptr
可以指向同一个对象,拷贝操作不会使原指针置空,这种特性使其能用于 STL 容器以及和auto_ptr
不相容的环境中。
- 使用注意事项:
auto_ptr
和tr1::shared_ptr
在析构函数中使用的是delete
而非delete []
,所以不能用于管理动态分配的数组(虽然代码能编译,但会导致错误的删除形式),若要管理动态分配数组,可考虑 Boost 库中的boost::scoped_array
和boost::shared_array
类。
4. 自定义资源管理类
- 预制的资源管理类(如
auto_ptr
和tr1::shared_ptr
)虽方便遵循 RAII 理念进行资源管理,但有时可能无法满足特定需求,这时就需要自行精心打造资源管理类,相关的注意事项和细节是 Item 14 和 15 的主题内容。
5. 函数接口与资源泄漏防范
- 像
createInvestment
这类返回裸指针的函数接口容易引发资源泄漏,因为调用者很容易忘记调用delete
,即便使用智能指针来管理,也得记住将返回值存储到智能指针对象中,解决此问题涉及改变函数接口,这是 Item 18 的主题内容。
6. 关键要点总结
- 为防止资源泄漏,要运用 RAII 对象,在其构造函数中获取资源,析构函数中释放资源。
tr1::shared_ptr
和auto_ptr
是常用的 RAII 实现类,通常tr1::shared_ptr
是更好选择,因其拷贝行为更符合直觉,而auto_ptr
拷贝时会置空自身。