如何写优雅的代码(1)——灵活使用goto和__try

来源:互联网 发布:如何看待网络信息安全 编辑:程序博客网 时间:2024/06/05 00:35
 

//========================================================================
//TITLE:
//    如何写优雅的代码(1)——灵活使用goto和__try
//AUTHOR:
//    norains
//DATE:
//    Thursday  16-July-2009
//Environment:
//    WINCE5.0 + VS2005
//========================================================================

    goto是毒药?凡是能用goto的地方,肯定能用结构化方式来实现相同的目的!估计很多朋友都对这论断不会陌生,甚至可以说,太熟悉了!但能实现并不代表优雅。不信?我们接下来看看。
   
    假设我们有一个函数,需要实现如下功能:将一个驱动某些内容读取到缓存区去;又因为该缓存是全局公用的,所以我们很自然采用互斥量来进行控制。首先,我们坚持采用结构化方式实现,很可能我们的代码类似如下:

view plaincopy to clipboardprint?
  1. BOOL ReadDeviceBuf()  
  2. {  
  3.     EnterCriticalSection(&g_csBuf);  
  4.   
  5.     //打开驱动设备   
  6.     HANDLE hDev = CreateFile(TEXT("DEV1:"),  
  7.         FILE_WRITE_ATTRIBUTES,  
  8.         0,  
  9.         NULL,  
  10.         OPEN_EXISTING,  
  11.         FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,  
  12.         NULL);  
  13.   
  14.     if(hDev == INVALID_HANDLE_VALUE)  
  15.     {  
  16.         LeaveCriticalSection(&g_csBuf);  
  17.         return FALSE;  
  18.     }  
  19.   
  20.     //获取驱动设备的缓存大小   
  21.     DWORD dwSize = 0;  
  22.     if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)  
  23.     {  
  24.         CloseHandle(hDev);  
  25.         LeaveCriticalSection(&g_csBuf);  
  26.         return FALSE;  
  27.     }  
  28.   
  29.     //分配缓存   
  30.     g_pBuf = new BYTE[dwSize];  
  31.     if(g_pBuf == NULL)  
  32.     {  
  33.         CloseHandle(hDev);  
  34.         LeaveCriticalSection(&g_csBuf);  
  35.         return FALSE;  
  36.     }  
  37.   
  38.     //从驱动中读取数据   
  39.     if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)  
  40.     {  
  41.         delete []g_pBuf;  
  42.         g_pBuf = FALSE;  
  43.   
  44.         CloseHandle(hDev);  
  45.         LeaveCriticalSection(&g_csBuf);  
  46.         return FALSE;  
  47.     }  
  48.   
  49.   
  50.     CloseHandle(hDev);  
  51.     LeaveCriticalSection(&g_csBuf);  
  52.   
  53.     return TRUE;  
  54. }  

 

    没错,采用这种结构化的方式的确是解决了问题。可是,我们是不是有点别扭呢?每次出错,返回FALSE之前,都必须要清理一次资源。小函数也许还不是什么大问题,只要睁大眼睛,小心翼翼,还是能在后续的返回中正确处理资源释放的。但如果函数因为要加入某些功能越来越大,又或许是别人来维护这段代码,那能保证在返回前释放资源么?
 
 
    接下来我们使用被大家鄙弃的goto,看看会发生什么情形:

view plaincopy to clipboardprint?
  1. BOOL ReadDeviceBuf()  
  2. {  
  3.     BOOL bRes = FALSE;  
  4.   
  5.     EnterCriticalSection(&g_csBuf);  
  6.   
  7.     DWORD dwSize = 0;  
  8.   
  9.     //打开驱动设备   
  10.     HANDLE hDev = CreateFile(TEXT("DEV1:"),  
  11.         FILE_WRITE_ATTRIBUTES,  
  12.         0,  
  13.         NULL,  
  14.         OPEN_EXISTING,  
  15.         FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,  
  16.         NULL);  
  17.   
  18.     if(hDev == INVALID_HANDLE_VALUE)  
  19.     {  
  20.         goto EXIT;  
  21.     }  
  22.   
  23.     //获取驱动设备的缓存大小   
  24.     //DWORD dwSize = 0; //产生编译错误:initialization of 'dwSize' is skipped by 'goto EXIT'  
  25.     if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)  
  26.     {  
  27.         goto EXIT;  
  28.     }  
  29.   
  30.     //分配缓存   
  31.     g_pBuf = new BYTE[dwSize];  
  32.     if(g_pBuf == NULL)  
  33.     {  
  34.         goto EXIT;  
  35.     }  
  36.   
  37.     //从驱动中读取数据   
  38.     if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)  
  39.     {  
  40.         goto EXIT;  
  41.     }  
  42.   
  43.     bRes = TRUE;  
  44.   
  45. EXIT:  
  46.   
  47.     if(bRes == FALSE)  
  48.     {  
  49.         delete []g_pBuf;  
  50.         g_pBuf = FALSE;  
  51.     }  
  52.   
  53.     CloseHandle(hDev);  
  54.     LeaveCriticalSection(&g_csBuf);  
  55.   
  56.     return bRes;  
  57. }  

 

    怎么样?把所有的资源释放都放到EXIT段中,每个EnterCriticalSection都能对应一个LeaveCriticalSection,是不是显得比之前的更为优雅?还能说goto为鸡肋么?
   
    不过,goto也不是尽善尽美,比如变量dwSize在goto之后就不能初始化,只能将局部变量的初始化放到第一个goto之前。按照C++的建议,变量的声明最好尽可能接近使用的地方。而放在第一个goto之前,摆明就是C作风嘛!
   
    其实如果以本特例,直接声明dwSize而不进行初始化也是可行的;但这并不代表在别的情况下也能畅通无阻,也许有的程序就依赖于初始化的值,谁知道呢?
   
   
    那有没有更为优雅的?可以解决这dwSize的位置问题的?答案自然是肯定的。不过,就必须请我们的__try出场咯:

view plaincopy to clipboardprint?
  1. BOOL ReadDeviceBuf()  
  2. {  
  3.     BOOL bRes = FALSE;  
  4.   
  5.     EnterCriticalSection(&g_csBuf);  
  6.   
  7.     //打开驱动设备   
  8.     HANDLE hDev = CreateFile(TEXT("DEV1:"),  
  9.             FILE_WRITE_ATTRIBUTES,  
  10.             0,  
  11.             NULL,  
  12.             OPEN_EXISTING,  
  13.             FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,  
  14.             NULL);  
  15.   
  16.     __try  
  17.     {  
  18.         if(hDev == INVALID_HANDLE_VALUE)  
  19.         {  
  20.             __leave;  
  21.         }  
  22.   
  23.         //获取驱动设备的缓存大小   
  24.         DWORD dwSize = 0;   
  25.         if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)  
  26.         {  
  27.             __leave;  
  28.         }  
  29.   
  30.         //分配缓存   
  31.         g_pBuf = new BYTE[dwSize];  
  32.         if(g_pBuf == NULL)  
  33.         {  
  34.             __leave;  
  35.         }  
  36.   
  37.         //从驱动中读取数据   
  38.         if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_pBuf,dwSize,NULL,NULL) == FALSE)  
  39.         {  
  40.             __leave;  
  41.         }  
  42.   
  43.         bRes = TRUE;  
  44.   
  45.     }  
  46.     __finally  
  47.     {  
  48.   
  49.         if(bRes == FALSE)  
  50.         {  
  51.             delete []g_pBuf;  
  52.             g_pBuf = FALSE;  
  53.         }  
  54.   
  55.         CloseHandle(hDev);  
  56.         LeaveCriticalSection(&g_csBuf);  
  57.     }  
  58.   
  59.     return bRes;  
  60. }  

 

    哦耶!现在dwSize终于在它该出现的位置上了,是不是显得比goto更为优雅呢?
   
    这段改写的代码采用的是SEH机制。因为SEH机制如果需要详细解释,就不是一言两语的事情,所以在此就略过,感兴趣的朋友可以自己在网上查找资料。在这里,只是说明一点,采用SEH机制,无论如何,最后基本上一定要运行__finally段代码,除非中间有中断。
   
   
    最后一段是不是意味着凡是可以运用goto的地方都能采用__try替代?答案是否定的。特别是代码中采用了STL,SEH机制将会无能为力。
   
    不信?我们添加一点代码段来看看事情的真相。假设我们不是通过数组来保留缓存,而是保留于STL的vector中,并且成功读取之后,我们还想输出每个数值,那么我们代码可以如下:

view plaincopy to clipboardprint?
  1. BOOL ReadDeviceBuf()  
  2. {  
  3.     BOOL bRes = FALSE;  
  4.   
  5.     EnterCriticalSection(&g_csBuf);  
  6.   
  7.     //打开驱动设备   
  8.     HANDLE hDev = CreateFile(TEXT("DEV1:"),  
  9.             FILE_WRITE_ATTRIBUTES,  
  10.             0,  
  11.             NULL,  
  12.             OPEN_EXISTING,  
  13.             FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,  
  14.             NULL);  
  15.   
  16.     __try  
  17.     {  
  18.         if(hDev == INVALID_HANDLE_VALUE)  
  19.         {  
  20.             __leave;  
  21.         }  
  22.   
  23.         //获取驱动设备的缓存大小   
  24.         DWORD dwSize = 0;   
  25.         if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)  
  26.         {  
  27.             __leave;  
  28.         }  
  29.   
  30.         //分配缓存   
  31.         g_vtBuf.resize(dwSize);  
  32.   
  33.         //从驱动中读取数据   
  34.         if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE)  
  35.         {  
  36.             __leave;  
  37.         }  
  38.   
  39.   
  40.         //打印每个数据   
  41.         //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding”  
  42.         for(std::vector<BYTE>::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++)  
  43.         {  
  44.             printf("%d/n",*iter);  
  45.         }  
  46.   
  47.         bRes = TRUE;  
  48.   
  49.     }  
  50.     __finally  
  51.     {  
  52.   
  53.         if(bRes == FALSE)  
  54.         {  
  55.             g_vtBuf.clear();  
  56.         }  
  57.   
  58.         CloseHandle(hDev);  
  59.         LeaveCriticalSection(&g_csBuf);  
  60.     }  
  61.   
  62.     return bRes;  
  63. }  

 


    很遗憾,这段代码无法编译通过。因为STL的迭代器中用到了对象,而对象会释放C++异常,而这和SEH有冲突。当然,我们完全可以用new来替代,以避开这个问题,但这样一来,却是使代码更峥嵘,离优雅更是八辈子打不到一个杆子上。
   
    这时候,还是只能用goto:

view plaincopy to clipboardprint?
  1. BOOL ReadDeviceBuf()  
  2. {  
  3.     BOOL bRes = FALSE;  
  4.   
  5.     EnterCriticalSection(&g_csBuf);  
  6.   
  7.     //打开驱动设备   
  8.     HANDLE hDev = CreateFile(TEXT("DEV1:"),  
  9.             FILE_WRITE_ATTRIBUTES,  
  10.             0,  
  11.             NULL,  
  12.             OPEN_EXISTING,  
  13.             FILE_ATTRIBUTE_NORMAL|FILE_FLAG_WRITE_THROUGH,  
  14.             NULL);  
  15.   
  16.   
  17.     if(hDev == INVALID_HANDLE_VALUE)  
  18.     {  
  19.         goto EXIT;  
  20.     }  
  21.   
  22.     //获取驱动设备的缓存大小   
  23.     DWORD dwSize = 0;   
  24.     if(DeviceIoControl(hDev,IOCTRL_BUFFER_SIZE,NULL,0,&dwSize,sizeof(dwSize),NULL,NULL) == FALSE)  
  25.     {  
  26.         goto EXIT;  
  27.     }  
  28.   
  29.     //分配缓存   
  30.     g_vtBuf.resize(dwSize);  
  31.   
  32.     //从驱动中读取数据   
  33.     if(DeviceIoControl(hDev,IOCTRL_GET_BUFFER,NULL,0,&g_vtBuf[0],g_vtBuf.size(),NULL,NULL) == FALSE)  
  34.     {  
  35.         goto EXIT;  
  36.     }  
  37.   
  38.   
  39.     //打印每个数据   
  40.     //这里无法编译通过,提示“error C2712: Cannot use __try in functions that require object unwinding”  
  41.     for(std::vector<BYTE>::iterator iter = g_vtBuf.begin(); iter != g_vtBuf.end(); iter ++)  
  42.     {  
  43.         printf("%d/n",*iter);  
  44.     }  
  45.   
  46.     bRes = TRUE;  
  47.   
  48. EXIT:  
  49.   
  50.   
  51.     if(bRes == FALSE)  
  52.     {  
  53.         g_vtBuf.clear();  
  54.     }  
  55.   
  56.     CloseHandle(hDev);  
  57.     LeaveCriticalSection(&g_csBuf);  
  58.   
  59.   
  60.     return bRes;  
  61. }  

 

    最后这个例子,从另一个角度说明了,goto并不一定是鸡肋,在某些特定的环境下,只有它才能拯救代码于优雅之境地。
   
   
    文章末尾,我们稍微总结一下。为了达到代码优雅的目的,我们首选__try;只有代码中用到了C++异常,导致和SEH冲突,我们才拿起饱受非议的goto,以完成我们优雅之目的。

原创粉丝点击