對於 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 裡面玩語法的部分也就差不多了。接下來將會注重功能性的東西。
摘自 溪流漫話