: 资源管理

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);

在资源管理类中,对于拷贝和赋值必须小心。通常有以下策略。

    1. 不允许拷贝。将拷贝构造函数设置为private
    1. 引用计数。类似于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; 
;
    1. 移交所有权。 类似于auto_ptr的实现,a=b则b不再占有该资源。
    1. 彻底地复制。通常需要深复制,确保不会出现差错。

15. 提供访问原始资源的函数

将资源封装成类之后,经常有必要提供一个对原始资源的访问函数:因为有可能会用到这些原始资源。(书中对这部分讲得比较啰嗦,我感觉其实不需要多解释这个问题)

不过一个需要思考的问题是:原始资源以怎样的方式暴露在外?
以下几个方法值得考虑:

  1. 使用一个get函数
  2. 重载对象的行为,使之和所管理的资源一致。
  3. 重载强制转换函数,使得封装对象可以转换为原始资源。这里需要注意是否允许隐式转换。

当然这几点并没有最佳做法,关键是视资源的使用环境而定。

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()); 

以上是关于: 资源管理的主要内容,如果未能解决你的问题,请参考以下文章

C/C++内存管理详解

C语言中如何装入资源文件? 用C++装入资源文件,可以做到。 现在我的其他程序必须在C模式下编译。

C语言(学生成绩管理系统)

c语言 商品管理系统

求图书馆管理系统程序(c语言)

c语言大作业 学生成绩管理系统