文中段落序号对应着书中的item编号。本文介绍item 13~17
本章主要介绍资源管理的一些思想和技巧。
什么是资源?简要地说就是用了之后必须归还的事物,比如说new申请的堆内存、文件、数据库连接、互斥锁等等。这些资源容易出现一些问题(比如使用后忘了释放),利用C++的一些机制可以有效避免。
先看这样一段代码:
Investment* createInvestment();//动态申请一个Investment对象,返回对象的指针void f()
{
Investment *pInv = createInvestment();
/*一些操作*/
delete pInv;
}
函数f
在执行时先申请了一个Investment对象,最后回收。看起来这没什么问题,对吧?
问题是中间的一些操作
,万一里面有return、exit、throw等等呢?类似的还有在循环体中的break、continue等。听起来这是正常人不会犯的低级错误,但是假如代码非常复杂呢?假如有个不很了解这段代码的人半路接手了这个代码呢?假如你时隔一年之后已经忘了自己怎么实现的,但又想优化一下代码呢?
这些事情非常容易发生。不过幸好,我们可以借助C++的类机制来管理这些资源。比如说将申请资源的代码写在构造函数中,释放资源的代码写在析构函数中。因为析构函数会自动调用,我们可以在析构函数中自动地回收资源。
auto_ptr是一个管理指针的对象。
如果使用auto_ptr来优化前面的f
函数,就变成了下面这样
void f()
{
std::auto_ptr pInv(createInvestment());
/*一些操作*/
}
pInv会在析构时自动delete Investment的指针。
不过要小心指针被重复delete的问题。假如有两个auto_ptr保存着同样的指针,那么该指针会被重复delete。
另外一个奇怪的地方是auto_ptr赋值后赋值的一方指针会变成null(拷贝构造函数同理)。
std::auto_ptr pInv1(createInvestment());
std::auto_ptr pInv2(pInv1); // pInv1现在变成null
pInv1 = pInv2; // pInv2变成null
由于STL的底层需要用到正常的赋值操作,auto_ptr不适合使用STL。
shared_ptr是一个引用计数智能指针(reference-counting smart pointer)。它会自动统计每个生成的对象有多少的引用,当引用数为0时再回收该对象。听起来像是垃圾回收机制,不过它不能处理环状引用问题。
void f()
{
...
std::tr1::shared_ptr
pInv(createInvestment());
/*一些操作*/
}
shared_ptr的赋值和拷贝构造函数就正常多了,也可以用在STL中。
auto_ptr和shared_ptr中的资源回收都是通过delete实现的,而不是delete[]。 也就是它不可以回收数组。
auto_ptr 和tr1::shared_ptr功能比较有局限性,很多时候我们需要创建自己的资源管理类。
以互斥锁为例。
void lock(Mutex *pm); // lock mutex pointed to by pm
void unlock(Mutex *pm); // unlock the mutex
如果不借助类,互斥锁应当这样实现:lock→临界代码区→unlock
如果借助资源管理类的实现如下所示:
定义类:
class Lock {
public:
explicit Lock(Mutex *pm)
: mutexPtr(pm)
{ lock(mutexPtr); } // acquire resource
~Lock() { unlock(mutexPtr); } // release resource
private:
Mutex *mutexPtr;
};
Mutex m; //定义互斥量
...
{
Lock ml(&m); //加锁
... //临界区
}
在前面的例子中,假如对Lock进行拷贝会怎么样?
Lock ml1(&m); // lock m
Lock ml2(ml1);
在资源管理类中,对于拷贝和赋值必须小心。通常有以下策略。
class Lock {
public:
explicit Lock(Mutex *pm) //
: mutexPtr(pm, unlock) // mutexPtr销毁时会调用unlock,而不是delete
{
lock(mutexPtr.get()); // item15会介绍"get"
}
private:
std::tr1::shared_ptr mutexPtr;
};
将资源封装成类之后,经常有必要提供一个对原始资源的访问函数:因为有可能会用到这些原始资源。(书中对这部分讲得比较啰嗦,我感觉其实不需要多解释这个问题)
不过一个需要思考的问题是:原始资源以怎样的方式暴露在外?
以下几个方法值得考虑:
当然这几点并没有最佳做法,关键是视资源的使用环境而定。
如果是new int[10], 对应delete []
如果是new int, 对应delete.
一定要注意匹配。
如果使用了typedef,一个变量可能看起来是单个元素实际上是一个数组,delete时尤其要注意。
先看一个反例:
/*
已知函数:
int priority();
processWidget(std::tr1::shared_ptr widget, int priority);
*/
processWidget(std::tr1::shared_ptr(new Widget), priority());
这一行代码可以通过编译,但其实是比较危险的。原因在于写在函数参数中的语句并没有确定的执行顺序。
在执行processWidget函数时,实际的调用顺序可能是:
假如priority()函数抛出异常,new Widget的资源就被泄漏了。
所以正确的写法是:
std::tr1::shared_ptr pw(new Widget);
processWidget(pw, priority());
上一篇:洛谷入门篇的相关题解
下一篇:Python中的猴子补丁