您的位置:首页 > 编程语言

编程中的资源管理(一)

2005-04-25 13:48 661 查看
编程中的资源管理

一、 编程中使用的资源

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

二、 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编程中使用的资源,如文件、数据库连接、网络连接等,还是需要程序员显式的进行释放资源。释放资源所使用的方法不再是析构函数,而是try、finally语句。

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

但是Java提供了try、finall语法结构,来完成确定性资源回收。语言保证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子句内,也使用了try,catch语句。请考虑如下情况:

① 如果函数f使用了两个资源:resource1、resource2。

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

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

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

四、下次讨论.NET中的Dispose模式、Using语句以及c++/cli中的确定性资源回收。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: