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