编程中的资源管理(一)

编程中的资源管理

 

 

 

 

 

 

 

一、             编程中使用的资源

 

 

 

 

 

 

 

在进行编程时,会用到各种各样的资源,比如文件、网络连接、数据库连接、信号量、事件、线程、内存等。在这里我们也把内存视为资源。这些资源都是非常珍贵的,不能无限期的拥有而不释放,好的编程习惯应该尽早释放这些资源,以下我们讨论使用什么方式来释放他们,以及在有异常的情况下如何保证释放资源。

 

 

 

 

 

 

 

二、             C++中利用析构函数

 

 

 

 

 

 

 

1.      直接释放资源

 

 

 

 

 

 

 

例子代码如下:

 

 

 

 

 

 

 

void f(const char* fn)

 

 

 

 

 

 

 

{

    // open fn for reading

 

 

 

 

 

 

 

FILE * pFile = fopen(fn, "r");

// use file

 

 

 

 

 

 

 

use_file( pFile )

 

 

 

 

 

 

 

    fclose( pFile ) ;

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

这里我们省略了use_file的处理逻辑。函数f的逻辑是很简单的,打开文件、使用文件,最后关闭文件。如果use_file函数正常结束,一切都没有问题,资源正常释放,但是如果use_file抛出了异常,那么文件将永远不会关闭。如果要修正这样的问题,直接的修改如下:

 

 

 

 

 

 

 

void f(const char* fn)

 

 

 

 

 

 

 

{

    // open fn for reading

 

 

 

 

 

 

 

    FILE * pFile = fopen(fn, "r");

 

 

 

 

 

 

 

    try {      

 

 

 

 

 

 

 

// use file

 

 

 

 

 

 

 

use_file( pFile )

 

 

 

 

 

 

 

        }

 

 

 

 

 

 

 

        catch(…) {

 

 

 

 

 

 

 

       fclose( pFile ) ;

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

    fclose( pFile ) ;

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

可以看到这样的修改引入了更多的代码,还有两处释放资源代码(红色代码)。如果函数内使用更多的资源,那么这将是很痛苦的。幸运的是C++提供了优雅的解决方案。

 

 

 

 

 

 

 

2.      使用RAII惯用法

 

 

 

 

 

 

 

C++语言中的一个惯用法是RAII (Resource acquisition is initialization) C++语言保证无论以何种方式退出函数,局部对象的析构函数总会被调用。其基本思想是使用局部对象代表资源,让局部对象的析构函数来释放资源。这样就保证了不会有资源泄漏、资源未释放的问题。

 

 

 

 

 

 

 

对于以上问题,C++创始人Bjarne Stroustrup的解决方案如下

 

 

 

 

 

 

 

class File_handle {

 

 

 

 

 

 

 

FILE* p;

 

 

 

 

 

 

 

public:

 

 

 

 

 

 

 

File_handle(const char* n, const char* a)

 

 

 

 

 

 

 

{ p = fopen(n,a); if (p==0) throw Open_error(errno); }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

File_handle(FILE* pp)

 

 

 

 

 

 

 

{ p = pp; if (p==0) throw Open_error(errno); }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

~File_handle() { fclose(p); }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

operator FILE*() { return p; }

 

 

 

 

 

 

 

// ...

 

 

 

 

 

 

 

};

 

 

 

 

 

 

 

void f(const char* fn)

 

 

 

 

 

 

 

{

    // open fn for reading

 

 

 

 

 

 

 

 

 

 

 

File_handle f(fn,"r");        

 

 

 

 

 

        // use file through f

 

 

 

 

use_file( f ) ;

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

这样无论函数f正常退出、还是因为异常而退出,C++都保证调用File_hanlle的析构函数,这样无论如何都将文件句柄关闭。可以看出函数f的代码清晰很多,而且更容易理解和使用,也更安全。

 

 

 

 

 

 

 

3.        应用的例子:

 

 

 

 

 

 

 

1)     C++标准库中的auto_ptr类。不同的是,这里的资源是内存。题外话,auto_ptr不能用于STL中的容器,不能使用它来释放new出来的数组。

 

 

 

 

 

 

 

2)     ATL中的CComAutoCriticalSection类,类的构造函数初始化临界区对象,析构函数释放临界区对象所使用的资源。

 

 

 

 

 

 

 

3)     STL中的basic_ifstream,basic_ofstream等都在析构函数时关闭文件句柄、释放资源。

 

 

 

 

 

 

 

三、             Java中利用Finally子句

 

 

 

 

 

 

 

使用Java语言,程序员确实可以不考虑内存泄露、内存溢出等头痛的问题了。但是对于在Java编程中使用的资源,如文件、数据库连接、网络连接等,还是需要程序员显式的进行释放资源。释放资源所使用的方法不再是析构函数,而是tryfinally语句。

 

 

 

 

 

 

 

由于内存模型的区别,在Java中没有提供C++析构函数的对等体,而提供了finalize方法。但是finalize方法仅在GC运行时才会被调用。此时可能距对象应该释放该资源已经很长时间了。导致了资源的无必要占用,降低了程序的可靠性和效率。也就是说用finalize方法来释放资源的时机是不确定的,而我们需要一种确定性的方式来释放资源。所以不能依赖于finalize方法来为我们完成释放资源的工作。

 

 

 

 

 

 

 

但是Java提供了tryfinall语法结构,来完成确定性资源回收。语言保证finally子句始终会被执行,无论try块因为异常退出还是正常退出。(有点儿象SHE__try__finally语法。)finally子句和C++的析构函数有相似的作用。

 

 

 

 

 

 

 

同样实现上面所描述的例子,代码如下:

 

 

 

 

 

 

 

void f(String fn) {

 

 

 

 

 

 

 

FileReader fr = new FileReader( fn ) ;

 

 

 

 

 

 

 

try {

 

 

 

 

 

 

 

// use file

 

 

 

 

 

 

 

use_file ( fr )

 

 

 

 

 

 

 

} finally {

 

 

 

 

 

 

 

   try {

      if ( fr )

         fr.close() ;

   }

   catch( IOException ) {}

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

注意finally子句内,也使用了trycatch语句。请考虑如下情况:

 

 

 

 

 

 

 

       如果函数f使用了两个资源:resource1resource2

 

 

 

 

 

 

 

       如果在释放第一个资源即resource1时发生了异常。

 

 

 

 

 

 

 

       那么就导致了释放resource2的代码不能够执行。

 

 

 

 

 

 

 

所以要保证第二个资源resource2能够释放,就必须保证释放第一个资源resource1时,不抛出异常。无独有偶,C++也要求在析构函数中不能够抛出异常,具体原因请参见[More Effective C++的条款11]

 

 

 

 

 

 

 

四、下次讨论.NET中的Dispose模式、Using语句以及c++/cli中的确定性资源回收。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值