条款13:以对象管理资源
1、常见的需要管理的资源有哪些?
- 最常用的资源:内存
- 文件描述器
- 互斥锁
- 图形界面中的字型和笔刷
- 数据库链接
- 网络sockets
2、传统的管理资源的方法的缺点
考虑以下情况:
class Investment{...}; //"投资类型",继承体系中的root class
假设这个程序库通过一个工厂函数提供我们特定的Investment对象
Investment* createInvesment(); //返回指针,指向Investment继承体系内的动态分配对象
下述函数使用该对象,由于其在动态内存中存储,因此使用者有责任在使用过后清理对象所占的动态内存:
void f()
{
Investment* pInv = createInvestment(); //调用factory
...
delete pInv; //释放pInv所指对象
}
这个函数存在以下问题,会导致delete pInv; 这条语句执行不了:
- 在该语句前出现了return时。
- 该语句在循环体内,而在此之前,出现了continue或者break时。
- 该语句之前出现了异常。
上述情况的任何一种出现(也许一开始写代码的人不会让这样的请款发生,但是后期维护时,可能导致上述某种情况。),都将导致delete pInv;语句无法被执行,即动态内存资源没有释放,因此发生了内存泄露。 因此,在函数内delete 语句是不保险的。
3、解决上述问题的方案
为确保我们使用资源以后一定会被释放,可以借助对象进行资源管理。大体的思路是:将使用的资源包含在对象内部,而由于对象来离开其有效作用域时一定会自动调用析构函数将其销毁,因此可以在析构函数中进行资源释放。
4、标准库中应用上述方案的例子1:auto_ptr
(1)功能:自动释放heap内存
auto_ptr 是个类似于指针的对象,也可以称为是智能指针,它指向的动态内存中分配的对象,会在使用后自动的(在析构函数中)回收内存。
原始代码的改进版:
void f()
{
...
std::tr1::shared_ptr<Investment>
pInv(createInvestment); //调用factroy,经由shared_ptr析构函数自动删除pInv
}
(2)实现aotu_ptr的两个关键想法:
- 获得资源后立刻放进管理对象。(RAII:资源获取时机便是初始化资源管理对象时机。)
- 管理对象运用析构函数确保资源被释放。
- 如果释放资源时导致异常,参考条款8。
(3)auto_ptr 智能指针对象的缺点:
由于auto_ptr对象在销毁时会回收它所指向的动态内存,因此不能让多个auto_ptr指向同一个对象,因为这会导致同一片内存被回收多次,因而产生不确定行为。
为了防止上述问题, auto_ptr有一个不寻常的性质:使用copy构造函数或者 copy 赋值函数复制他们,他们会变成null,而复制所得物指向原本的auto_ptr指向的内存。
(4)由于上述性质, auto_ptr的使用场景:
要求正常复制行为的对象,不可以使用auto_ptr。比如STL的容器。
5、标准库中应用上述方案的例子2:trl::shared_ptr
为解决上述智能指针的缺陷,可以采用:RCSP:引用计数型指针。
(1)基本原理:
这个智能指针内部有一个计数器,记录着几个智能指针指向了同一个对象,只有在没有剩余的智能指针指向某一对象时,才回回收它。
(2)缺点:
无法解决环状问题:即A指向B,B指向A。 这时二者都不能被释放。
6、上述两种智能指针的使用注意事项
上述智能指针对象,在其析构函数中做:delete x; 而不是delete x[ ]。因此动态分配的数组不能使用上述两种指针。
标准库中没有类似的指针对象,来解决动态分配的数据的资源释放问题。实际上,使用vector 和string 就够了。
7、关于资源管理类最后的话
如果上述的智能指针不够我们用,那么我们也可以写出自己想要的智能指针,但是需要考虑一些细节。这些细节在条款14和条款15中描述。