0%

ScopeExit and Higher Order Function in C++

Story

故事起源來自於看到類似這樣的程式碼

1
2
3
4
5
6
7
8
9
10
11
12
#define VL_RESTORER(var) \
const VRestorer<typename std::decay<decltype(var)>::type> restorer_##var(var);

template <typename T> class VRestorer {
T& m_ref;
const T m_saved;
public:
explicit VRestorer(T& permr)
: m_ref{permr}
, m_saved{permr} {}
~VRestorer() { m_ref = m_saved; }
};

利用RAII來保存上下文當前的值,執行到結束的時候恢復原狀
不過

1
2
3
4
5
int a = 1, b = 2;
VL_RESTORER(a);
VL_RESTORER(b);
a = 3;
b = 4;

用起來沒什麼問題,不過總要找個題目來練習

ScopeExit

基本上就是RAII的變形,在Destructor的部分執行我們需要的Function,隨便在github搜尋就一堆了,這邊有個最簡單的方案

1
2
3
4
5
6
7
8
9
10
11
12
template <typename F>
struct scope_exit
{
F f;
~scope_exit() { f(); }
};

template <typename F>
inline scope_exit<F> make_scope_exit(F&& f)
{
return scope_exit<F>{f};
}

如果使用上C++17的CTAD,底下的make_scope_exit也不一定得存在

所以問題就變成了這樣,我希望在結束的時候,將所存的變數恢復原狀
問題就變成了該怎麼做

Higher Order Function

雖然C++不是標準的Functional Programming Language,不過要做點手腳還是辦得到的
問題變成了,傳入需要保存狀態的變數,回傳是一個函數,執行這個函數就能恢復原狀,這裡用上了Variadic Template和Tuple

1
2
3
4
5
6
7
8
9
template <typename ...Ts>
inline auto restore(Ts&& ...ts)
{
return [restore_ref = std::tuple<std::add_lvalue_reference_t<std::decay_t<Ts>>...>(std::forward<Ts>(ts)...),
store = std::tuple<std::add_const_t<std::decay_t<Ts>>...>(ts...)]() mutable noexcept
{
restore_ref = store;
};
}

這邊有兩個tuple,其中restore_ref保存了所有變數的reference,store則是變數這個時間點的值

Combo

上面的方式能夠寫成

1
2
3
4
int a = 1, b = 2;
auto _ = make_scope_exit(restore(a, b));
a = 3;
b = 4;

好壞就見仁見智了