这种代码结构如何组织?goto or do…while(0)?

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

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

(1号方案)

<span class="hljs-function">BOOL <span class="hljs-title">foo</span><span class="hljs-params">()</span>
</span>{
    BOOL bRet = FALSE;

    HANDLE hProcess = OpenProcess(...);

    <span class="hljs-keyword">if</span> (hProcess != <span class="hljs-literal">NULL</span>)
    {
        HANDLE hToken = OpenProcessToken(hProcess, ...);

        <span class="hljs-keyword">if</span> (hToken != <span class="hljs-literal">NULL</span>)
        {
            <span class="hljs-comment">// ...</span>

            <span class="hljs-keyword">if</span> (LookupPrivilegeValue(...))
            {
                <span class="hljs-keyword">if</span> (AdjustTokenPrivileges(hToken, ...))
                {
                    bRet = TRUE;
                }
            }

            CloseHandle(hToken);
        }

        CloseHandle(hProcess);
    }

    <span class="hljs-keyword">return</span> bRet;
}

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

(2号方案)

<span class="hljs-function">BOOL <span class="hljs-title">foo</span><span class="hljs-params">()</span>
</span>{
    HANDLE hProcess = OpenProcess(...);

    <span class="hljs-keyword">if</span> (hProcess == <span class="hljs-literal">NULL</span>)
    {
        <span class="hljs-keyword">return</span> FALSE;
    }

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

    <span class="hljs-keyword">if</span> (hToken == <span class="hljs-literal">NULL</span>)
    {
        CloseHandle(hProcess);

        <span class="hljs-keyword">return</span> FALSE;
    }

    <span class="hljs-comment">// ...</span>

    <span class="hljs-keyword">if</span> (!LookupPrivilegeValue(...))
    {
        CloseHandle(hToken);
        CloseHandle(hProcess);

        <span class="hljs-keyword">return</span> FALSE;
    }

    <span class="hljs-keyword">if</span> (!AdjustTokenPrivileges(hToken, ...))
    {
        CloseHandle(hToken);
        CloseHandle(hProcess);

        <span class="hljs-keyword">return</span> FALSE;
    }

    CloseHandle(hToken);
    CloseHandle(hProcess);

    <span class="hljs-keyword">return</span> TRUE;
}

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

do…while(0):

(3号方案)

<span class="hljs-function">BOOL <span class="hljs-title">foo</span><span class="hljs-params">()</span>
</span>{
    HANDLE hProcess = OpenProcess(...);

    <span class="hljs-keyword">if</span> (hProcess == <span class="hljs-literal">NULL</span>)
    {
        <span class="hljs-keyword">return</span> FALSE;
    }

    BOOL bRet = FALSE;

    <span class="hljs-keyword">do</span> 
    {
        HANDLE hToken = OpenProcessToken(hProcess, ...);

        <span class="hljs-keyword">if</span> (hToken == <span class="hljs-literal">NULL</span>)
        {
            <span class="hljs-keyword">break</span>;
        }

        <span class="hljs-comment">// ...</span>

        BOOL bRetInner = FALSE;

        <span class="hljs-keyword">do</span> 
        {
            <span class="hljs-keyword">if</span> (!LookupPrivilegeValue(...))
            {
                <span class="hljs-keyword">break</span>;
            }

            <span class="hljs-keyword">if</span> (!AdjustTokenPrivileges(hToken, ...))
            {
                <span class="hljs-keyword">break</span>;
            }

            bRetInner = TRUE;

        } <span class="hljs-keyword">while</span> (<span class="hljs-number">0</span>);

        CloseHandle(hToken);

        <span class="hljs-keyword">if</span> (!bRetInner)
        {
            <span class="hljs-keyword">break</span>;
        }

        bRet = TRUE;

    } <span class="hljs-keyword">while</span> (<span class="hljs-number">0</span>);

    CloseHandle(hProcess);

    <span class="hljs-keyword">return</span> bRet;
}

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

goto:

(4.1号方案)

<span class="hljs-function">BOOL <span class="hljs-title">foo</span><span class="hljs-params">()</span>
</span>{
    BOOL bRet = FALSE;

    HANDLE hProcess = OpenProcess(...);

    <span class="hljs-keyword">if</span> (hProcess == <span class="hljs-literal">NULL</span>)
    {
        <span class="hljs-keyword">goto</span> CLEAR;
    }

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

    <span class="hljs-keyword">if</span> (hToken == <span class="hljs-literal">NULL</span>)
    {
        <span class="hljs-keyword">goto</span> CLEAR;
    }

    <span class="hljs-comment">// ...</span>

    <span class="hljs-keyword">if</span> (!LookupPrivilegeValue(...))
    {
        <span class="hljs-keyword">goto</span> CLEAR;
    }

    <span class="hljs-keyword">if</span> (!AdjustTokenPrivileges(hToken, ...))
    {
        <span class="hljs-keyword">goto</span> CLEAR;
    }

    bRet = TRUE;

CLEAR:
    <span class="hljs-keyword">if</span> (hToken != <span class="hljs-literal">NULL</span>)
    {
        CloseHandle(hToken);
    }

    <span class="hljs-keyword">if</span> (hProcess != <span class="hljs-literal">NULL</span>)
    {
        CloseHandle(hProcess);
    }

    <span class="hljs-keyword">return</span> bRet;
}

(4.2号方案)

<span class="hljs-function">BOOL <span class="hljs-title">foo</span><span class="hljs-params">()</span>
</span>{
    BOOL bRet = FALSE;

    HANDLE hProcess = OpenProcess(...);

    <span class="hljs-keyword">if</span> (hProcess == <span class="hljs-literal">NULL</span>)
    {
        <span class="hljs-keyword">goto</span> ERROR_LEVEL0;
    }

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

    <span class="hljs-keyword">if</span> (hToken == <span class="hljs-literal">NULL</span>)
    {
        <span class="hljs-keyword">goto</span> ERROR_LEVEL1;
    }

    <span class="hljs-comment">// ...</span>

    <span class="hljs-keyword">if</span> (!LookupPrivilegeValue(...))
    {
        <span class="hljs-keyword">goto</span> ERROR_LEVEL2;
    }

    <span class="hljs-keyword">if</span> (!AdjustTokenPrivileges(hToken, ...))
    {
        <span class="hljs-keyword">goto</span> ERROR_LEVEL2;
    }

    bRet = TRUE;

ERROR_LEVEL2:
    CloseHandle(hToken);
ERROR_LEVEL1:
    CloseHandle(hProcess);
ERROR_LEVEL0:
    <span class="hljs-keyword">return</span> bRet;
}

(4.1和4.2哪种好一点。。。?)

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

<span class="hljs-function">BOOL <span class="hljs-title">foo</span><span class="hljs-params">()</span>
</span>{
    HRESULT hr = CoInitializeEx(<span class="hljs-number">0</span>, COINIT_MULTITHREADED); 

    <span class="hljs-keyword">while</span> (<span class="hljs-literal">true</span>)
    {
        <span class="hljs-keyword">if</span> (FAILED(hr))
        {
            <span class="hljs-keyword">break</span>;
        }

        hr = CoInitializeSecurity(<span class="hljs-literal">NULL</span>, <span class="hljs-number">-1</span>, <span class="hljs-literal">NULL</span>, <span class="hljs-literal">NULL</span>, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, <span class="hljs-literal">NULL</span>, EOAC_NONE, <span class="hljs-literal">NULL</span>);

        <span class="hljs-keyword">if</span> (FAILED(hr))
        {
            <span class="hljs-keyword">break</span>;
        }

        CComPtr<IWbemLocator> pLoc = <span class="hljs-literal">NULL</span>;
        hr = pLoc.CoCreateInstance(CLSID_WbemLocator, <span class="hljs-literal">NULL</span>, CLSCTX_INPROC_SERVER);

        <span class="hljs-keyword">if</span> (FAILED(hr))
        {
            <span class="hljs-keyword">break</span>;
        }

        CComPtr<IWbemServices> pSvc = <span class="hljs-literal">NULL</span>;
        hr = pLoc->ConnectServer(_T(<span class="hljs-string">"ROOT\\CIMV2"</span>), <span class="hljs-literal">NULL</span>, <span class="hljs-literal">NULL</span>, <span class="hljs-literal">NULL</span>, <span class="hljs-number">0</span>, <span class="hljs-literal">NULL</span>, <span class="hljs-literal">NULL</span>, &pSvc);

        <span class="hljs-keyword">if</span> (FAILED(hr))
        {
            <span class="hljs-keyword">break</span>;
        }

        hr = CoSetProxyBlanket(pSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, <span class="hljs-literal">NULL</span>, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, <span class="hljs-literal">NULL</span>, EOAC_NONE);

        <span class="hljs-keyword">if</span> (FAILED(hr))
        {
            <span class="hljs-keyword">break</span>;
        }

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

        <span class="hljs-keyword">if</span> (FAILED(hr))
        {
            <span class="hljs-keyword">break</span>;
        }

        ULONG uCount = <span class="hljs-number">1</span>;
        CComPtr<IWbemClassObject> pNext = <span class="hljs-literal">NULL</span>;
        hr = pEnum->Next(WBEM_INFINITE, uCount, &pNext, &uCount);

        <span class="hljs-keyword">if</span> (FAILED(hr))
        {
            <span class="hljs-keyword">break</span>;
        }

        <span class="hljs-comment">// ...</span>

        <span class="hljs-keyword">break</span>;
    }

    CoUninitialize();

    <span class="hljs-keyword">return</span> SUCCEEDED(hr);
}

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

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

谢谢!


首发:http://www.cppblog.com/Streamlet/archive/2010/03/30/110955.html