资源释放 (转)

资源释放 (转)[@more@]

资源释放

问题

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

int TextFileLines(const char* szTxtFile)XML:namespace prefix = o ns = "urn:schemas-microsoft-com:Office:office" />

{

  //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

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

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

  CFuncPack(FuncType pFunc,ArgType1 arg1)

  {

    m_pInstance=new InFuncPack1(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


来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10752019/viewspace-962487/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/10752019/viewspace-962487/

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值