程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 利用已有的bind構造ScopeExit

利用已有的bind構造ScopeExit

編輯:C++入門知識

對於 ScopeExit,以前有提到過(見《這種代碼結構如何組織?goto or do…while(0)?》http://www.BkJia.com/kf/201205/132632.html)。使用場景再簡單提一下:
bool GenFile()
{
    HANDLE hFile = CreateFile(_T("Test.txt"), GENERIC_WRITE, 0, NUL, CREATE_ALWAYS, 0, NULL);
 
    if (hFile == INVALID_HANDLE_VALUE)
    {
        return false;
    }
 
    CString strData = _T("test");
    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
    DWORD dwWritten = 0;
 
    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
    {
        CloseHandle(hFile);
        return false;
    }
 
//     if (...)
//     {
//         CloseHandle(hFile);
//         return false;
//     }
//
//     ...
//
 
    CloseHandle(hFile);
 
    return true;
}
 
如上面這部分代碼,如果 if … 之類的流程持續下去(如注釋部分),每個 return false 之前都得帶上 CloseHandle(),非常累贅。因此,出現了類似的 ScopeExit。boost 裡有一個 BOOST_SCOPE_EXIT,Loki 裡面也有一個 ScopeGuard。
繼續使用剛才的案例,BOOST_SCOPE_EXIT 用法:
bool GenFile()
{
    HANDLE hFile = CreateFile(_T("Test.txt"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 
    if (hFile == INVALID_HANDLE_VALUE)
    {
        return false;
    }
 
    BOOST_SCOPE_EXIT((hFile))
    {
        CloseHandle(hFile);
    }
    BOOST_SCOPE_EXIT_END
 
    CString strData = _T("test");
    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
    DWORD dwWritten = 0;
 
    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
    {
        return false;
    }
 
//     if (...)
//     {
//         return false;
//     }
//
//     ...
//
 
    return true;
}
 
這樣,每個 return 之前再也不必背負 CloseHandle 的包袱。
Loki::ScopeGuard 的用法:
bool GenFile()
{
    HANDLE hFile = CreateFile(_T("Test.txt"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 
    if (hFile == INVALID_HANDLE_VALUE)
    {
        return false;
    }
 
    LOKI_ON_BLOCK_EXIT(CloseHandle, hFile);
 
    CString strData = _T("test");
    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
    DWORD dwWritten = 0;
 
    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
    {
        return false;
    }
 
//     if (...)
//     {
//         return false;
//     }
//
//     ...
//
 
    return true;
}
 
從使用的簡潔程度上看,Loki 更勝一籌。
另外,我們經常也遇到有條件的執行清理動作的情形,boost 和 Loki 都支持。先看 Loki 的使用案例:
bool GenFile()
{
    LPCTSTR FILE_NAME = _T("Test.txt");
    HANDLE hFile = CreateFile(FILE_NAME, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 
    if (hFile == INVALID_HANDLE_VALUE)
    {
        return false;
    }
 
    Loki::ScopeGuard sgDeleteFile = Loki::MakeGuard(DeleteFile, FILE_NAME);
    LOKI_ON_BLOCK_EXIT(CloseHandle, hFile);
 
    CString strData = _T("test");
    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
    DWORD dwWritten = 0;
 
    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
    {
        return false;
    }
 
//     if (...)
//     {
//         return false;
//     }
//
//     ...
//
 
    sgDeleteFile.Dismiss();
 
    return true;
}
 
一開始,我們使用具名的 ScopeGuard,綁定了一個 DeleteFile(FILE_NAME) 的操作,到最後通過 Dismiss,讓此操作不被執行。
相應地,boost 中,可以在進入 scope exit 之前設定一個變量,將此變量捕獲入 scope exit,到最後給這個變量賦值,決定執不執行:
bool GenFile()
{
    LPCTSTR FILE_NAME = _T("Test.txt");
    HANDLE hFile = CreateFile(FILE_NAME, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 
    if (hFile == INVALID_HANDLE_VALUE)
    {
        return false;
    }
 
    bool bOK = false;
 
    BOOST_SCOPE_EXIT((hFile)(&bOK))
    {
        if (!bOK)
        {
            CloseHandle(hFile);
        }
    }
    BOOST_SCOPE_EXIT_END
 
    CString strData = _T("test");
    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
    DWORD dwWritten = 0;
 
    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
    {
        return false;
    }
 
//     if (...)
//     {
//         return false;
//     }
//
//     ...
//
 
    bOK = true;
 
    return true;
}
注意,此處捕獲 bOK 的時候才用指針的形式,以保證最後對此變量的修改能影響到 scope exit 內部。
好,預備知識簡要介紹到這裡。下面我們進入正題,實現一個類似的東西。
為啥要放在現在說這個事情呢?因為前幾天剛弄了個 Bind,使用 Bind 實現 Scope Exit 比較簡單。從 boost 和 Loki 的兩個方案來看,個人更喜歡 Loki 的,boost 玩語法玩得太厲害。通常我們這種需求都是一兩行代碼(比如上面例子中的 CloseHandle,DeleteFile之類的),而 boost 方案是用來填寫一大段代碼的,一兩行代碼的情形下用起來不方便,重復的框架性代碼就占了四行,有效功能才占一行,性價比太低了。
Loki 的實現中,有對於多個參數的處理,搞出了 ScopeGuardImpl0、ScopeGuardImpl1、ScopeGuardImpl2、…… 這在 Loki 中沒有類似 boost::bind 的設施的情況下是必須的。這裡插播一句,Loki 的 Bind 相當的不好用,只能綁定第一個,然後再綁定第一個,…,從而才能完成對所有參數的綁定。boost::bind 靈活多了。有了類似 boost::bind 的設施後,我們利用它山寨一個 Loki::ScopeGuard。當然,我現在是在寫 xl::ScopeExit,當然會用 xl::Bind 去實現。本文暫不限定是 xl::Bind 還是 boost::bind。設個預編譯開關吧:
#define USING_BOOST_BIND
 
#ifdef USING_BOOST_BIND
#define BOOST_BIND_ENABLE_STDCALL
#include <boost/bind.hpp>
#define SCOPE_EXIT_BIND ::boost::bind
#else
#include <xl/Meta/xlBind.h>
#define SCOPE_EXIT_BIND ::xl::Bind
#endif
高亮的那句是為了打開 boost::bind 對 __stdcall 調用約定的支持。然後抄 Loki 的 ScopeGuardImplBase:
class ScopeGuardImplBase
{
public:
    ScopeGuardImplBase() : m_bDismissed(false)
    {
 
    }
 
    ScopeGuardImplBase(ScopeGuardImplBase &that) :
        m_bDismissed(that.m_bDismissed)
    {
        that.Dismiss();
    }
 
    ~ScopeGuardImplBase()
    {
 
    }
 
protected:
    template <typename J>
    static void StaticExecute(J &j)
    {
        if (!j.m_bDismissed)
        {
            j.Execute();
        }
    }
 
public:
    void Dismiss() const
    {
        m_bDismissed = true;
    }
 
private:
    mutable bool m_bDismissed;
};
 
typedef const ScopeGuardImplBase& ScopeGuard;
 
Loki 在 j.Execute 中有 try … catch …,個人認為不該有(覺得 Loki::ScopeGuard 似乎不該在它自己裡面 try … catch …
ScopeGuard 只是幫我們調用一個函數而已,至於這個函數是否有異常出來,它不該悄悄地把它吞了,而應該還我們本來面目,不知道是不是?可是為什麼幾乎所有介紹 ScopeGuard 的文章都說這 try … catch … 用得好呢?

),所以去掉。
然後對於 ScopeGuardImpl,我們拋開 0、1、2,使用一個超級簡潔的實現:
template <typename F>
class ScopeGuardImpl : public ScopeGuardImplBase
{
public:
    ScopeGuardImpl(F fn) :
        ScopeGuardImplBase(), m_fn(fn)
    {
 
    }
 
    ~ScopeGuardImpl()
    {
        StaticExecute(*this);
    }
 
    void Execute()
    {
        m_fn();
    }
 
private:
    F m_fn;
};
我們只需要一個參數,一個兼容函數簽名“void ()”的可執行對象 F fn。我們保存這個 fn 直到析構的時候去執行它。
已經差不多了,為了使用簡便,再抄一個 MakeGuard:
template <typename F>
inline ScopeGuardImpl<F> MakeGuard(F f)
{
    return ScopeGuardImpl<F>(f);
}
 
最後,定義一個可供匿名使用的宏 XL_ON_BLOCK_EXIT:
#define XL_CONN_(s, t)  s##t
#define XL_CONN(s, t)   XL_CONN_(s, t)
#define XL_ON_BLOCK_EXIT(...) ScopeGuard XL_CONN(sg, __LINE__) = MakeGuard(SCOPE_EXIT_BIND(__VA_ARGS__))
同 Loki 一樣,我們使用行號作為“匿名”變量的命名。注意 MakeGuard 後的參數裡,需要填寫如整個 bind,這在具名使用的時候書寫上會麻煩一點點。
好了,已實現完畢,我們再一次用前面的例子來使用它:
bool GenFile()
{
    LPCTSTR FILE_NAME = _T("Test.txt");
    HANDLE hFile = CreateFile(FILE_NAME, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
 
    if (hFile == INVALID_HANDLE_VALUE)
    {
        return false;
    }
 
    ScopeGuard sgDeleteFile = MakeGuard(SCOPE_EXIT_BIND(DeleteFile, FILE_NAME));
    XL_ON_BLOCK_EXIT(CloseHandle, hFile);
 
    CString strData = _T("test");
    DWORD dwToWrite = strData.GetLength() * sizeof(TCHAR);
    DWORD dwWritten = 0;
 
    if (!WriteFile(hFile, (LPCTSTR)strData, dwToWrite, &dwWritten, NULL) || dwWritten != dwToWrite)
    {
        return false;
    }
 
//     if (...)
//     {
//         return false;
//     }
//
//     ...
//
 
    sgDeleteFile.Dismiss();
 
    return true;
}
 
高亮部分,就是比 Loki 使用起來麻煩的地方。如果明確了是使用 boost::bind 或 xl::Bind,那就寫成:
ScopeGuard sgDeleteFile = MakeGuard(boost::bind(DeleteFile, FILE_NAME));

ScopeGuard sgDeleteFile = MakeGuard(xl::Bind(DeleteFile, FILE_NAME));
(有個區別是,如果綁定成員函數,boost::bind 的對象指針在第二個參數,xl::Bind 在第一個參數。)
 
好了,寫到這裡。請各位指教。
至此,xlLib 裡面玩語法的部分也就差不多了。接下來將會注重功能性的東西。

 


摘自 溪流漫話

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved