条款13:以对象管理资源
(use objects to manage resources.)
内容:
我们还是以例子来说明问题,假设我们使用一个图片资源的类Image:
class Image{...};
接下来我们需要一个工厂函数供应我们特定的Image对象:
Image* createImage();//返回创建的Image对象的地址。
这里我们有一个隐性的协议:如果客户使用该工厂函数来获得Image对象,当你使用完了后,你有责任去删除它。
而如果违反了这个协议,那么显然造成了"资源浪费"等一些列问题,客户一般这样用这个对象:
{
Image* pcurImage=createImage();
...
delete pcurImage;
}
看起来一切都很良好,但是有些情况下就会出问题:如果在"..."代码区段出现return,goto,continue等语句时,我
们往往会忘记delete pcurImage;这句代码;如果执行到释放代码之前有异常抛出,我们就没有机会去释放申请的资源,
...;潜在的危险太多,太可怕了,客户根本就不好去预期哪种情况会出现,现在该怎么办?我们这里推荐的一种解决方
案是:运用对象来管理资源。为什么会想到把资源放到对象里面呢?由于对象超出它的作用域时候会自动调用析构函数,
我们只要在析构函数中调用它的释放资源函数就可以了!想法不错吧?呵呵,而在用法上我们要注意两点:(1)被管理对
象创建的时候要立即被放入对象中,这种观点被称为"资源取得时机便是初始化时机"(Resource Acquisition Is
Initialization;RAII);(2)管理对象(managing object)运用析构函数确保资源被释放。
标准库中auto_ptr就是对这种情形设置的特制产品,也就是"只能指针",下面我们来示范如何使用它:
{
std::auto_ptr<Image> aptrImage(createImage());
... //退出作用域的时候自动调用析构函数释放占用的资源
}
怎么样?所有问题都解决了吧,但这里我们要注意一下auto_ptr的用法,它的copying 构造和copying assign操作
函数有点奇怪,看下面代码:
{
using namespace std;
auto_ptr<Image > aptrImage1(createImage()); //aptrImage1指向createImage()返回的对象
auto_ptr<Image > aptrImage2(aptrImage1); //aptrImage2指向对象,aptrImage1指向null
aptrImage1 = aptrImage2; //aptrImage1指向对象,aptrImage2指向null
...
}
这里的经过copying构造和copying assign操作函数后,原自动对象管理器就失去了指向原先拥有对象的机会,而将
此机会交给了目标自动管理对象,自己指向null对象。也就是说"auto_ptr管理的资源必须绝对没有一个以上的auto_ptr同时指向它",从
另外一方面说明auto_ptr也非管理动态分配资源的最佳工具。于是auto_ptr的替代方案出来了,它就是"引用计数型智慧指针"(
reference-couting smart pointer;RCSP).其实就是在目标对象上引用计数机制,当引用计数为0时,说明没有其它对象在
引用它,它就对目标对象进行资源回收,伪代码形如下:
void xxx::release(){
if(--refCount == 0){
delete this;
}
}
TR1的tr1::shared_ptr就是个RCSP,你可以这么用它:
{
...
using std::tr1;
shared_ptr<Image > sptrImage1(createImage()); //sptrImage1指向createImage()的返回值对象
shared_ptr<Image > sptrImage2(sptrImage1); //sptrImage2与sptrImage1指向同一个对象
sptrImage1 = sptrImage2; //同上
... //sptrImage1与sptrImage2与他们所指向对象被自动销毁。
}
你看,这样的复制行为就正常的多了,是不是?呵呵,ok,it's over!
请记住:
〓 为防止资源泄露,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。
〓 两个常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是较佳选择,因为其copying行为比较直观。若选择auto_ptr,复制动作会使它(被复制物)指向null.