Boost.ScopeExitの実装をのぞいてみる

これぐらいなら俺でも余裕で作れると思ったらそうは問屋が卸さなかった。


void f() {
int n = 0;
vector<int> v;

BOOST_SCOPE_EXIT)((n)(&v))( {
v.push_back(n);
cout << v[0] << endl;
} BOOST_SCOPE_EXIT_END
}

このコードをBoost.Preprocessorに通してからそれを超意訳&簡単化するとこんなようなコードが出来上がる。
なお、BOOST_TYPEOFに関しては今回注目しているところではないし、展開しても分かりにくいだけなのでそのままにした。


// scope_exit.hpp

namespace exist { namespace scope_exit {

typedef void (*val_tag)(int);
typedef void (*ref_tag)(int&);
template<typename T, typename tag> struct member;
template<typename T>
struct member<T, val_tag> {
T value;
member(T & val) : value(val) {}
};

template<typename T>
struct member<T, ref_tag> {
T & value;
member(T & val) : value(val) {}
};

template<typename T>
T& deref(T & ref) { return ref; }
template<typename T>
T& deref(T * ptr) { return *ptr; }

}}

// user code (preprocessed)

void f() {
int n = 0;
std::vector<int> v;

struct exist_scope_exit_t_42 {
typedef BOOST_TYPEOF(exist::scope_exit::deref(n)) scope_exit_capture_t_0;
typedef BOOST_TYPEOF(exist::scope_exit::deref(&v)) scope_exit_capture_t_1;

typedef void (*tag_0)(int n);
typedef void (*tag_1)(int &v);

exist::scope_exit::member<scope_exit_capture_t_0, tag_0> capture_0;
exist::scope_exit::member<scope_exit_capture_t_1, tag_1> capture_1;

exist_scope_exit_t_42(
scope_exit_capture_t_0& cap_0
, scope_exit_capture_t_1& cap_1
) :
capture_0(cap_0)
, capture_1(cap_1)
{}

~exist_scope_exit_t_42() {
scope_exit_body(
capture_0.value
, capture_1.value
);
}

static void scope_exit_body(
scope_exit_capture_t_0 n
, scope_exit_capture_t_1 &v
)

{
v.push_back(n);
cout << v[0] << endl;
}

} exist_scope_exit_42(
exist::scope_exit::deref(n)
, exist::scope_exit::deref(&v)
);
}

このうち、繰り返しっぽくなっている部分はBoost.Preprocessor(主にBOOST_PP_SEQ_ENUM_PP)を使って展開する。
42ってなってる部分は、定義されるスコープ内でScopeExitのために宣言した名前が衝突しないようにするためにつけられている
(ので、ユニークであればなんでも良い。ただし、ユーザーがScopeExitを書くたびに自動生成できる必要がある)

BOOST_TYPEOF(&v) &vとかでクラス中にメンバとしてそのまま型と名前を持ち込めると思ったのだが、それだと初期化子で初期化するようにプリプロセッサで展開できないのでうーんと唸った挙句 Boost.ScopeExit の実装を見たら、別にユーザの書くコードがそのままデストラクタである必要はないのかと超絶に納得。
でもなんでBoostの実装は一旦ポインタを作ってごにょごにょしてるんだろう?そういや = {...};で初期化する版のworkaroundがあったな。そのためか?

プリプロセッサは実に恐ろしい。