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;
|
好壞就見仁見智了