随笔-90  评论-947  文章-0  trackbacks-0

灰常感谢各位达人昨天的热心回帖,让我受益匪浅。我仰望夜空,群星点点,就如各位的点睛之语,在无尽的苍穹闪耀。这让我深深地意识到,在这里,不仅可以分享成果,也可以分享困惑、分享寂寞。(开场白到此结束~)

在平常的编程中,我发现很容易遇到这种结构:

(1号方案)

BOOL foo()
{
   
BOOL bRet = FALSE;

   
HANDLE hProcess = OpenProcess(...);

   
if (hProcess != NULL)
    {
       
HANDLE hToken = OpenProcessToken(hProcess, ...);

       
if (hToken != NULL)
        {
           
// ...

           
if (LookupPrivilegeValue(...))
            {
               
if (AdjustTokenPrivileges(hToken, ...))
                {
                   
bRet = TRUE;
                }
            }

           
CloseHandle(hToken);
        }

       
CloseHandle(hProcess);
    }

   
return bRet;
}

如上写法,容易造成缩进级别不断增加。为了避免这种情况,可以改成:

(2号方案)

BOOL foo()
{
   
HANDLE hProcess = OpenProcess(...);

   
if (hProcess == NULL)
    {
       
return FALSE;
    }

   
HANDLE hToken = OpenProcessToken(hProcess, ...);

   
if (hToken == NULL)
    {
       
CloseHandle(hProcess);

       
return FALSE;
    }

   
// ...

   
if (!LookupPrivilegeValue(...))
    {
       
CloseHandle(hToken);
       
CloseHandle(hProcess);

       
return FALSE;
    }

   
if (!AdjustTokenPrivileges(hToken, ...))
    {
       
CloseHandle(hToken);
       
CloseHandle(hProcess);

       
return FALSE;
    }

   
CloseHandle(hToken);
   
CloseHandle(hProcess);

   
return TRUE;
}

这样,又引来了新的问题,每次 return FALSE 时的清理任务比较麻烦,要是每步操作都引进新的 HANDLE 的话,后续的清理工作就变得非常繁重。有人推荐do…while(0)的结构,有人推荐goto。这两种形式分别是——

do…while(0)

(3号方案)

BOOL foo()
{
   
HANDLE hProcess = OpenProcess(...);

   
if (hProcess == NULL)
    {
       
return FALSE;
    }

   
BOOL bRet = FALSE;

   
do
   
{
       
HANDLE hToken = OpenProcessToken(hProcess, ...);

       
if (hToken == NULL)
        {
           
break;
        }

       
// ...

       
BOOL bRetInner = FALSE;

       
do
       
{
           
if (!LookupPrivilegeValue(...))
            {
               
break;
            }

           
if (!AdjustTokenPrivileges(hToken, ...))
            {
               
break;
            }

           
bRetInner = TRUE;

        }
while (0);

       
CloseHandle(hToken);

       
if (!bRetInner)
        {
           
break;
        }

       
bRet = TRUE;

    }
while (0);

   
CloseHandle(hProcess);

   
return bRet;
}

这种结构可以避免每次 return FALSE 前的一堆清理工作,但缺点是,有几个依赖性的 HANDLE,就要嵌套几层的 do…while(0),有时候也会遇到需要三四层嵌套的情形。

goto

(4.1号方案)

BOOL foo
()
{
   
BOOL bRet = FALSE;

   
HANDLE hProcess = OpenProcess(...);

   
if (hProcess == NULL)
    {
       
goto CLEAR;
    }

   
HANDLE hToken = OpenProcessToken(hProcess, ...);

   
if (hToken == NULL)
    {
       
goto CLEAR;
    }

   
// ...

   
if (!LookupPrivilegeValue(...))
    {
       
goto CLEAR;
    }

   
if (!AdjustTokenPrivileges(hToken, ...))
    {
       
goto CLEAR;
    }

   
bRet = TRUE;

CLEAR:
   
if (hToken != NULL)
    {
       
CloseHandle(hToken);
    }

   
if (hProcess != NULL)
    {
       
CloseHandle(hProcess);
    }

   
return bRet;
}
(4.2号方案)

BOOL foo
()
{
   
BOOL bRet = FALSE;

   
HANDLE hProcess = OpenProcess(...);

   
if (hProcess == NULL)
    {
       
goto ERROR_LEVEL0;
    }

   
HANDLE hToken = OpenProcessToken(hProcess, ...);

   
if (hToken == NULL)
    {
       
goto ERROR_LEVEL1;
    }

   
// ...

   
if (!LookupPrivilegeValue(...))
    {
       
goto ERROR_LEVEL2;
    }

   
if (!AdjustTokenPrivileges(hToken, ...))
    {
       
goto ERROR_LEVEL2;
    }

   
bRet = TRUE;

ERROR_LEVEL2:
   
CloseHandle(hToken);
ERROR_LEVEL1:
   
CloseHandle(hProcess);
ERROR_LEVEL0:
   
return bRet;
}

(左边和右边哪种好一点。。。?)

在这种情形下,goto 的方案似乎是完美的。但是 goto 如果遇到 C++,缺点体现出来了。下面这一段,现在是 do…while(0) 结构(只有一层嵌套,这种结构用在这里还算合理):

BOOL foo()
{
   
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);

   
while (true)
    {
       
if (FAILED(hr))
        {
           
break;
        }

       
hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL);

       
if (FAILED(hr))
        {
           
break;
        }

       
CComPtr<IWbemLocator> pLoc = NULL;
       
hr = pLoc.CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER);

       
if (FAILED(hr))
        {
           
break;
        }

       
CComPtr<IWbemServices> pSvc = NULL;
       
hr = pLoc->ConnectServer(_T("ROOT\\CIMV2"), NULL, NULL, NULL, 0, NULL, NULL, &pSvc);

       
if (FAILED(hr))
        {
           
break;
        }

       
hr = CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE);

       
if (FAILED(hr))
        {
           
break;
        }

       
CComPtr<IEnumWbemClassObject> pEnum = NULL;
       
_bstr_t bstrLang = _T("WQL");
       
_bstr_t bstrSql = _T("SELECT * FROM __InstanceCreationEvent WITHIN 10")
           
_T("WHERE TargetInstance ISA 'Win32_LogonSession' AND (TargetInstance.LogonType = 2 OR TargetInstance.LogonType = 11)");
       
hr = pSvc->ExecNotificationQuery(bstrLang, bstrSql, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnum);

       
if (FAILED(hr))
        {
           
break;
        }

       
ULONG uCount = 1;
       
CComPtr<IWbemClassObject> pNext = NULL;
       
hr = pEnum->Next(WBEM_INFINITE, uCount, &pNext, &uCount);

       
if (FAILED(hr))
        {
           
break;
        }

       
// ...

       
break;
    }

   
CoUninitialize();

   
return SUCCEEDED(hr);
}

如果改成 goto,则需要把所有需要对象的定义全放到最前面来,不然 goto 会跳过他们的初始化,编译不过。但是,所有对象都放到最前面定义,又违反了即用即声明的规则,而且太多了也容易混淆。

最后,问题是,如果遇到 C++ 的、多层嵌套的,大家一般如何组织代码呢?

谢谢!

posted on 2010-03-30 09:55 溪流 阅读(2760) 评论(26)  编辑 收藏 引用 所属分类: C++

评论:
# re: 这种代码结构如何组织?goto or do&hellip;while(0)?[未登录] 2010-03-30 10:02 | v
封装,让它自己析构  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)?[未登录] 2010-03-30 10:18 | ~
个人觉得第二种最明了.

本来就不可避免的问题,为什么要想法设法去避免呢?  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 10:38 | 陈昱(CY)
这个没办法解开的,逻辑复杂时,不可能 既完全没有重复代码,又效率最高(思路最清晰)的

觉得1、2种都可以  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)?[未登录] 2010-03-30 10:54 | Dancefire
我的观点是这样:

1、如果只有5层左右if嵌套,不是什么大问题。可以用一个函数解决,看到不少代码中都是存在一个函数中有多层if嵌套。

2、如果嵌套太多,影响函数逻辑的清晰了。那么拆解函数,将功能独立的内层嵌套,拆解成独立的函数。保证每个函数中嵌套不要太多,而拆解出的函数,功能又比较独立。

3、对于需要重复写每次清理时繁琐的步骤的问题,如果确实重复、确实繁琐,可写成一个函数,每次清理前调用一下这个函数即可。不用担心调用成本,C++会自动分析,编译Release版本时,很有可能会将其视为inline。

4、假如Process和Token的每次创建过程都比较复杂,比如有多种情况要考虑,多种错误要处理,出现问题后要有多种错误处理方式。那么建议将Process和/或Token进行类封装。这样使用时,只要Process p;即可,发现错误直接return false; p会自动析构,调用相应的析构函数来清理垃圾。在这种情况下,我与第一个回复的朋友观点一致。不要担心类的成本,只要没有继承、虚函数之类的东西,类的效率与调用函数无太大差别。

5、使用异常。异常的设计初衷本就是为了解决这种现象的,避免错误处理影响了函数逻辑。如果出了错就是异常处理、清理、返回,而不会出现改正错误重新执行代码的情况,可以使用异常。这样只要出错就抛出异常,不同的错误,异常不同。函数内解决异常,不让异常出函数。这样也可以将错误处理集中起来。  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 11:17 | 空明流转
一般很少出现这种情况。。。
偶尔用while(0),如果是一些特殊的析构语义,那就用Functor+一个Execute on Destruction。不过这个也有弊端,就是不能扔异常。  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 12:45 | 鹰击长空
@Dancefire
同意大部分 但个人不喜欢使用异常   回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 13:00 | 溪流
@v
不会每个HANDLE都搞个class吧?这样这个class连一个相对完整的逻辑都没包含  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 13:00 | 溪流
@~
我想确定下是不是真的无法避免了。。  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 13:01 | 溪流
@陈昱(CY)
效率不重要,代码没重复、思路清晰就可以了  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 13:02 | 溪流
@Dancefire
@鹰击长空

我也不喜欢异常~  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 13:03 | 溪流
@空明流转
很少吗?像文中的这两种场合如何解决呢?Windows API应该经常会有这种场合吧,除非你不做应用层的开发。。  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 14:51 | yrj
观点与第一个回复的朋友一致

"不会每个HANDLE都搞个class吧?这样这个class连一个相对完整的逻辑都没包含"

Google Chromium 源码里就有不少这样的 class
http://src.chromium.org/svn/trunk/src/base/scoped_handle_win.h  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 15:28 | 溪流
@yrj
谢谢,这个例子让我耳目一新~  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 15:31 | 溪流
忽然觉得有点想通了,其实就是需要一个“智能句柄”。。。  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 15:46 | OwnWaterloo
大哥, C++有析构函数, 根本不会写出这种代码。
而且, 也不需要一个handle一个RAII类, 有范型的Loki::ScopeGuard, 有boost::scope_exit, 爱怎么玩就怎么玩。

C语言, do while(0) 一层就够了。
注意利用不可能的handle值。
  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 16:01 | 陈梓瀚(vczh)
把你的HANDLE封装在类里,用析构函数去搞定  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 16:06 | 溪流
@OwnWaterloo
大哥哥,do...while(0)一层怎么搞呀?可否给个示范?就用3号方案的场景好了~~  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 16:44 | OwnWaterloo
cppblog插入代码很繁琐……
你看是这样吗?
int f(void)
{
      HANDLE process 
= 0;
      HANDLE token   
= 0;
      
int r = 0;

      
do {

            
if ( process = OpenProcess(), !process)
                  
break;
            
if ( token = OpenProcessToken(), !token )
                  
break;
            
if ( !LookupPrivilegeValue( ) )
                  
break;
            
if ( !AdjustTokenPrivileges( ) )
                  
break;

            r 
= 1;

      }
 while (0)


      
if ( token != N(OpenProcessToken) ) CloseHandle(token);
      
if ( process!= N(OpenProcess) ) CloseHandle(process);
      
return r;
}

  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 17:44 | yrj
纯 C 特例

如果调用的每个函数返回同一种错误码, 可用宏和goto (4.1号方案)

#define check_error(func) if ((error = (func)) < 0) goto Error;

int foo()
{
int error;

check_error(func1());

check_error(func2());

check_error(func3());

/* */

Error:
/* your cleanup code */
return error;
}  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 17:48 | megahertz
封装再封装,每个函数只做一件事。对象管理资源分配和释放  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 18:55 | 溪流
看到这里,我觉得可以接受的做法就是给每种需要清理的对象封装一个类了:
ScopedHandle
ScopedCoInitialization
ScopedSysMemory
ScopedPointer
CComPtr
。。。  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 19:00 | OwnWaterloo
@溪流
不是给你说了有范型的RAII(RRID)类吗?
 
int f()
{
      HANDLE process 
= OpenProcess(  );
      
if (!process)
            
return 0;
      LOKI_ON_BLOCK_EXIT(process, CloseHandle);

      HANDLE token 
= OpenProcessTokon(  );
      
if (!token)
            
return 0;
      LOKI_ON_BLOCK_EXIT(token, CloseHandle);

      
if ( !LookupPrivilegeValue(  ) )
            
return 0;

      
if ( !AdjustTokenPrivileges(  ) )
            
return 0;

      
return 1;
}

  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 19:12 | 溪流
@OwnWaterloo
啊,,,这是哪个库里的?  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 19:18 | 溪流
@OwnWaterloo
哦,,,又是boost。。。  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-30 19:22 | OwnWaterloo
@溪流
是Loki啦:
http://en.wikipedia.org/wiki/Loki_(C%2B%2B)
http://loki-lib.sourceforge.net/

配套的《modern c++ design》是目前看过的最高级的C++书……

boost也有scope_exit,是重量级的。
Loki的ScopeGuard依赖"现有的函数", 比如CloseHandle。
注册之后, 在block退出时, 会调用这个函数。

而boost scope_exit是"就地编写退出代码"(调用函数也包括在内)。
当退出时, 会执行这些代码。
  回复  更多评论
  
# re: 这种代码结构如何组织?goto or do&hellip;while(0)? 2010-03-31 00:18 | 溪流
@OwnWaterloo
我突然也有想写一个的冲动。。。  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理