読者です 読者をやめる 読者になる 読者になる

とくにあぶなくないRiSKのブログ

危ないRiSKのブログだったかもしれない。本当はRiSKだけどググラビリティとか取得できるIDの都合でsscriskも使ったり。

C++0x 時代の ScopeGuard (scope guard)

scope_guard - melpon日記 - C++すら(ry
今なら C++0x が動くコンパイラがあるので,私のへぼい知識を総動員して元ネタを書き直してみた。

#if !defined(SSCRISK_SCOPE_GUARD_HPP)
#define SSCRISK_SCOPE_GUARD_HPP
#if defined(_MSC_VER) && _MSC_VER >= 1020
#pragma once
#endif

// scope_guard.hpp

#include<utility>
#include<type_traits>

namespace sscrisk{

 template<class F>
 class scope_guard
 {
  F f;
  bool dismissed;
 public:
  template<class G> explicit scope_guard(G&& f): f(std::forward<G>(f)), dismissed(false){}
  scope_guard(scope_guard const &) = delete;
  scope_guard(scope_guard&& sg): f(std::move(sg.f)), dismissed(sg.dismissed){sg.dismiss();}
  ~scope_guard(){if(!dismissed)f();}
  void dismiss(){dismissed = true;}
 };
 
 template<class F>
 scope_guard<typename std::remove_cv<typename std::remove_reference<F>::type>::type> make_guard(F&& f)
 {
  return scope_guard<typename std::remove_cv<typename std::remove_reference<F>::type>::type>(std::forward<F>(f));
 }

}

#endif

こんな感じで使う

#include<cstdio>
#include<vector>
#include<cstdlib>
#include<sscrisk/scope_guard.hpp>

using sscrisk::make_guard;

void named_success_test() // 識別子あり、正常終了
{
 FILE* const f = fopen("a.cpp", "r");
 auto guard = make_guard(std::move([f]{if(f)fclose(f); puts("fclose(f)");}));
 puts("do something...");
}

void temporary_failure_test() // 一時オブジェクト、例外あり
{
 FILE* const f = fopen("a.cpp", "r");
 make_guard([f]{if(f)fclose(f); puts("fclose(f)");}), []{
  puts("do something...");
  for(int i = 0; i != 2; ++i)
  {
   printf("%d\n", i);
   if(i == 1)throw "error!";
  }
 }();
}

void exception_safe_test(std::vector<int>& v) // 例外安全
{
 v.push_back(42);
 auto guard = make_guard([&]{v.pop_back(); puts("rollback");});
 puts("do something...");
 if(rand() % 2)throw "error!"; // 例外発生なのでguardによりロールバックされる
 guard.dismiss(); // 正常終了なのでロールバックキャンセル
} // 正常終了なのでロールバックなし

int main()
{
 try
 {
  named_success_test();
  temporary_failure_test();
 }
 catch(char const * s){puts(s);}

 std::vector<int> v;
 try
 {
  exception_safe_test(v);
 }
 catch(char const * s){puts(s);}
 // v は常に正常な状態
}

実行結果(例):

do something...
fclose(f)
do something...
0
1
fclose(f)
error!
do something...
rollback
error!

正常終了時,例外発生時ともに fclose が呼ぶこと(scope exit)ができますし,dismissを使って例外発生時のみ rollback させること(scope failure)もできます。
ライブラリの実装はあんまり自信ないのですが,perfect forwarding とか move とかこれでいいのかな?何かあればコメントください。

追記:

先人たちの歩みがすでにあったりする。
Togetter - 「C++ ScopeGuard 0x」
私が出る幕ではないですねw