资源释放

类别:VC语言 点击:0 评论:0 推荐:
资源释放 问题

正如软件可以抽象为信息采集、信息处理和信息输出一样,对于单个函数,同样可以抽象为资源申请、资源使用和资源释放。这些资源包括内存、网络连接、文件局柄等。就象以下代码所示(为简化代码,忽略了错误处理):

int TextFileLines(const char* szTxtFile)

{

      //declare arguments

      HANDLE hFile;

      HANDLE hMap;

      char* lpBuf;

      int cbLines;

 

      //acquire resources

      hFile=CreateFile(szTxtFile,GENERIC_READ,0,0,OPEN_EXISTING,0,0);

      hMap=CreateFileMapping(hFile,NULL,PAGE_READONLY,0,0,NULL);

      lpBuf=(char*)MapViewOfFile(hMap,FILE_MAP_READ,0,0,0);

 

      //use resources,calculate number of lines in buffer

      cbLines =CalcLines(lpBuf);

 

      //clean up resources

      UnmapViewOfFile(lpBuf);

      CloseHandle(hMap);

      CloseHandle(hFile);

 

      return cbLines;

}

然而,资源的释放却远不是想象中的容易,就像上面的代码中所示,所有的资源看似都已经释放,但实际上却隐含着许多的问题:

1、如果在CalcLines函数中发生异常,在此之后的所有代码都无法运行,资源发生泄漏。

2、随着代码的增加,我们必须在函数的每一个退出点上明确地进行各个资源的释放工作。

3、随着代码的增加,非常容易忘记进行资源的释放工作。

4、由于各系统资源间存在一定的内在关系,如某一资源依赖另一资源的情况,释放多个系统资源时应遵循一定的顺序,即最后申请的资源需要最先释放。

显然,我们需要一种机制,自动解决这些问题。

解决

C++的类机制是解决这些问题的自然选择。我们可以在类的构造函数中登记申请的系统资源,在类的析构函数中释放系统资源(就像auto_ptr一样)。首先,C++语言确保在离开函数的作用域时,包括因发生异常而导致的中断,对应的局部对象都会得到正确的析构,这样就保证了资源的申请和资源的释放必然会成对出现,不会发生资源的泄漏。其次,C++语言确保在局部对象的析构过程中,类析构顺序与类构造顺序相反,即最后声明的局部对象会最先得以析构,这样就确保了资源的释放顺序。最后,我们建立一个编码约定,即在申资源成功后,马上进行资源的释放登记工作,这样就解决了资源释放的遗忘问题。

确定采用类机制后,针对每种具体资源,我们不难设计出一个具体的类,实现系统资源的自动释放,就像MFC中的类一样。但这样容易导致类数量增多、代码膨胀、名字空间得到污染。我们需要一个更轻量级的、更通用的机制。类模板便是用来解决这种问题的方法。

然而,还有许多问题。首先,针对不同的资源,对应的资源释放函数的原型、参数数量和参数类型都各不相同,这就需要数量可变的模板参数、数量可变的函数参数,但遗憾的事,C++中不存在“数量可变的模板参数”,因为参数类型不同,也无法使用“数量可变的函数参数”。我们只能凭借经验,确定有限个数的参数,本文约定为4个。其次,C++中不允许同名而参数个数不同的模板类存在,既不存在模板类重载机制,我们只有针对不同的参数数量,分别命名为Func1、Func2…,但那样对于用户而言,会带来很大的麻烦。我们需要重新封装为统一的名字。

实现

按照以上思路,我们的第一次定义可能是这样(针对一个参数):

template<typename PFN,typename ARG1>

class CFuncPack1

{

public:

      CFuncPack1(PFN pfn,ARG1 arg1) : m_pfn(pfn),m_arg1(arg1) {}

      ~CFuncPack1()

         {

                    m_pfn(m_arg1);

         }

      PFN      m_pfn;

      ARG1     m_arg1;

};

其调用方法如下:

int main()

{

      HANDLE handle=CreateFile(…);

      CFuncPack1< BOOL (WINAPI*)(HANDLE),HANDLE > aFunc(CloseHandle,handle);

      return 0;

}

以上便实现了文件局柄资源的自动释放。但从以上客户的调用方式来看,需要用户明确指定模板类的参数类型,尤其是第一个参数为函数指针,指定其类型就像一场恶梦。同时,用户还需根据参数数量的不同明确指定不同的模板类。那么有没有更简单的方法,让客户调用时更简单,就像以下的方式一样呢。

int main()

{

      HANDLE handle=CreateFile(…);

      CfuncPack  aFunc(CloseHandle,handle);

      return 0;

}

答案当然是肯定的。首先,我们可以使用一个统一的包装类CFuncPack来封装不同参数的模板类CFuncPack1、CFuncPack2…。虽然C++中不允许同名而参数个数不同的模板类存在,但允许存在同名而参数个数不同的模板函数存在,即模板函数重载,包括模板构造函数,在模板函数中生成不同的模板类。其次,在模板函数的调用过程中,用户可以使用C++语言提供的模板参数推导而无需手工指定参数的具体类型。最终的实现如下(针对一个参数):

class CFuncPack

{

//base class

struct InFuncPackBase

{

         virtual void DirectCall(BOOL bClear=true)=0;         //Manual Call

         virtual ~InFuncPackBase(){};                             //virtual destructor

         virtual void clear()=0;                                                //reset

};

 

//template class of one parameter

template<typename FuncType,typename ArgType1>

struct InFuncPack1 : public InFuncPackBase

{

           InFuncPack1(FuncType pFunc,ArgType1 arg1) : m_pFunc(pFunc) , m_arg1(arg1) {}

         ~InFuncPack1() { DirectCall(); }

         virtual void DirectCall(BOOL bClear=true)

           {

                           if(m_pFunc)

                             {

                                      m_pFunc(m_arg1);

                             }

                             if(bClear)

                                      m_pFunc=NULL;

           }

         virtual void clear (){m_pFunc=NULL;}

                    FuncType         m_pFunc;                  

                    ArgType1         m_arg1;           

         };

 

//Constructor of one parameter

template<typename FuncType,typename ArgType1>

      CFuncPack(FuncType pFunc,ArgType1 arg1)

      {

           m_pInstance=new InFuncPack1<FuncType,ArgType1>(pFunc,arg1);

      }

      void clear(){m_pInstance->clear();}

      InFuncPackBase*     m_pInstance; //pointer to derived class

};

运用

使用以上CFuncPack模板类后,本文开始处的TextFileLines函数代码可改为如下,相对于原来代码,在代码未有明显扩充的情况下,系统资源的释放有了充分的保证。

int TextFileLines(const char* szTxtFile)

{

      //declare arguments

      HANDLE hFile;

      HANDLE hMap;

      char* lpBuf;

      int cbLines;

 

      //acquire resources

      hFile=CreateFile(szTxtFile,GENERIC_READ,0,0,OPEN_EXISTING,0,0);

      CFuncPack aFile(CloseHandle,hFile);

      hMap=CreateFileMapping(hFile,NULL,PAGE_READONLY,0,0,NULL);

      CFuncPack aMap(CloseHandle,hMap);

      lpBuf=(char*)MapViewOfFile(hMap,FILE_MAP_READ,0,0,0);

      CFuncPack aMapofView(UnmapViewOfFile,lpBuf);

 

      //use resources,calculate number of lines in buffer

      cbLines =CalcLines(lpBuf);

 

      return cbLines;

}

其实还可以做的更多。看到clear函数吗?它能使函数支持“事务功能”。譬如使用WinCE的对象存储技术进行项目数据的生成函数如下:

bool CreateProject(LPCTSTR szDBFile,LPCTSTR szDesignFile)

{

         //Create Database

CEGUID GuidDB;

         CeMountDBVol(&GuidDB, szDBFile,CREATE_NEW);

CFuncPack aFile(DeleteFile, szDBFile);

CFuncPack aDB(CeUnmountDBVol,&GuidDB);

 

//Create Tables

HANDLE hTable1=CreateTable(….);           //Create Table 1

CFuncPack aTable1(CloseHandle,hTable1);

HANDLE hTable2=CreateTable(….);           //Create Table 2

CFuncPack aTable2(CloseHandle,hTable2);

 

//Import design data

bool bRes=ImportDesignFile(szDesignFile);

if(!bRes)

return FALSE;

 

//Reserve resource(database)

aTable2.clear();

aTable1.clear();

aDB.clear();

aFile.clear();

 

return true;

}

在上面的函数中,如果所有的操作都成功完成,则项目数据库和数据库表均会得到保存;否则,数据库表和数据库将会关闭,数据库文件将会删除,一切都会回到开始时的状态,不会留下和泄露任何资源,达到“事务处理”的功能。

以上代码,在Win2000+SP2、Visual C++6.0上通过编译。

 

 

 

 

 

 

胡乐秋

2003-3-27

本文地址:http://com.8s8s.com/it/it1428.htm