BOOST_SCOPE_EXIT_ENDは消せないのか?
http://twitter.com/cpp_akira/status/5995895644
中身は追ってないけど、 BOOST_SCOPE_EXIT_END はどうにかならないのかなー、とか
http://twitter.com/DecimalBloat/status/5996160887
SCOPE_EXIT_ENDを消すのは無理っぽいです。詳しく書くとややこしいので書きませんが、簡単に説明すると、ScopeExitの関数本体より後ろにキャプチャ変数名を伝えないと、 ScopeExit関数オブジェクトを初期化できませんので、それをうまいことやってやる必要があります。
http://twitter.com/DecimalBloat/status/5996233281
ただし、C++0xのプリプロセッサには、__VA_ARGS__が入るので、(int x, y; 以下略) という形式で、関数本体をSCOPE_EXITマクロの引数に取るようにすれば消せます。
http://twitter.com/DecimalBloat/status/5996331916
あぁ、もう一つ。現行のC++でも、 (BEGIN 関数本体 END)みたいな形式で関数本体をSCOPE_EXITマクロの引数に取れば、現行のC++でもいけます。
※私(DecimalBloat)の回答の発言内容が間違っています。が、きっかけなので一応載せておきます。詳しくは以下に記しますが、本当はキャプチャ変数うんぬんに関係なく、ScopeExitオブジェクトを初期化するコードを生成しないといけないので必要、ということです。
現在のScopeExitの構文では消せないです。実は過去に私も正確に把握せずにこういう話を書いたのですが、かなり不正確なので、この際もう一度正確に、かつ端折って説明したいと思います(矛盾)。
Boost.ScopeExitのおおまかな仕組み
void f(int x) { int y = 0; BOOST_SCOPE_EXIT((x)(y)) { // ScopeExitの関数本体 std::cout << x + y << std::endl; std::cout << "Goodbye World..." << std::endl; } BOOST_SCOPE_EXIT_END }
これがプリプロセス時に展開されて、どうにかなればいいわけです。C++でスコープを抜けるときに発動する何かと言えばデストラクタ、というのは、界隈では常識なので、それを利用した展開結果を考えてみます。
void f(int x) { int y = 0; struct scope_exit_ ## __LINE__ ## _type { // スコープ内で一意な型名を付ける // (x)(y)から、↓のようなメンバ変数宣言を生成 BOOST_TYPEOF(外のx) x; BOOST_TYPEOF(外のy) y; scope_exit_ ## __LINE__ ## _type( // (x)(y)から、↓のようなコンストラクタのパラメータリストを生成 BOOST_TYPEOF(外のx) x, BOOST_TYPEOF(外のy) y ) : // (x)(y)から、↓のような初期化子リストを生成 x(x), y(y) {} ~scope_exit_ ## __LINE__ ## _type // ここまでBOOST_SCOPE_EXIT // ここに関数本体。上の例だと、{ std::cout <<< x + y ... } // ここからBOOST_SCOPE_EXIT_END } scope_exit_ ## __LINE__ ## _obj = { // スコープ内で一意な名前を付ける // (x)(y)から、↓のようなコンストラクタ引数リストを生成 x, y }; }
ScopeExitは、同じスコープ内でいくつも作ることができます。そのため、ScopeExitの型とオブジェクトは、作るごとに一意な名前にする必要があります。スコープ内でユニークな型やオブジェクトの名前は、scope_exit_ ## __LINE__ ## _type などとすれば、狙って同じ名前を使おうとしない限り、まず大丈夫です。上のコードの scope_exit_ ## __LINE__ ## _type は、例えばscope_exit_123_typeなどに展開された結果だと思ってください。BOOST_TYPEOF(外のx) x;の部分は、今は関係ないので、後で説明します。
まず、関数本体より下のコードを生成しないといけないので、これでBOOST_SCOPE_EXITが必要であることが分かります。それから、(x)(y)というのをマクロの引数に取っているので、これを使って、型の中で同じ名前の変数を宣言して、デストラクタから参照できるようにします。各メンバ変数は、対応する変数で初期化しないといけないので、コンストラクタの引数とか初期化子リストも自動生成します。
が、どのみちこの手法では無理です。ご覧の通り、scope_exit_ ## __LINE__ ## _objの初期化には(x)(y)のキャプチャ変数リストが必要ですが、BOOST_SCOPE_EXIT_ENDにはそれがありません。ついでに言うと、BOOST_SCOPE_EXIT_ENDがないと、そもそも関数本体より後ろのコードが生成できないことになります。
で、現行のC++でどのようにして解決しているのかというと、
void f(int x) { int y = 0; struct scope_exit_ ## __LINE__ ## _type { // スコープ内で一意な型名を付ける // (x)(y)から、↓のようなメンバ変数宣言を生成 BOOST_TYPEOF(外のx) * x; BOOST_TYPEOF(外のy) * y; scope_exit_ ## __LINE__ ## _type( // (x)(y)から、↓のようなコンストラクタのパラメータリストを生成 BOOST_TYPEOF(外のx) x, BOOST_TYPEOF(外のy) y ) : // (x)(y)から、↓のような初期化子リストを生成 x(x), y(y) {} } scope_exit_ ## __LINE__ ## _obj = { // スコープ内で一意な名前を付ける // (x)(y)から、↓のようなコンストラクタ引数リストを生成 &x, &y }; boost::scope_exit::holder_t<sizeof(holder)>::apply<0> holder; holder = &scope_exit_ ## __LINE__ ## _obj; struct scope_exit_ ## __LINE__ ## _type_2 { // 上記の型同様にメンバ変数宣言 BOOST_TYPEOF(外のx) x; BOOST_TYPEOF(外のy) y; scope_exit_ ## __LINE__ ## _type_2 ( ::boost::scope_exit::holder_t holder ) : // (x)(y)から、↓のようなパラメータリストを生成 x(*static_cast<TYPEOF(x)*>(holder->ptr), y(*static_cast<TYPEOF(y)*>(holder->ptr) {} ~scope_exit_ ## __LINE__ ## _type_2 // ここまでBOOST_SCOPE_EXIT // 関数本体 // ここからBOOST_SCOPE_EXIT_END } scope_exit_ ## __LINE__ ## _obj_2 = holder; }
おおざっぱにはこのような構造になっています。本物のScopeExitは、参照キャプチャを実現するために、さらに手のこんだ事をやっているのですがこの際それはいいです。知りたい方はBoost.ScopeExitを自分の手で展開してみてください。
こうすることで、二つ目のオブジェクトは必ずholderという名前の値で初期化できるので、関数本体以降にキャプチャ変数名を伝えなくても済むようになりました。
ところで、Boost.ScopeExitは、一つのスコープ中に何度書いてもいいはずです。そのためにわざわざ型やオブジェクトの名前を一意にしているのですから。ところが、何度もScopeExitを書くと、そのたびにholder_t
あと、BOOST_TYPEOF(外のx)の部分ですが、
// (x)(y)から↓のようなtypedefを生成 typedef scope_exit_ ## __LINE__ ## _capture_type_1 BOOST_TYPEOF(x); typedef scope_exit_ ## __LINE__ ## _capture_type_2 BOOST_TYPEOF(y); struct scope_exit_ ## __LINE__ ## _type { // 型の外でtypedefした型名を中に持ち込み scope_exit_ ## __LINE__ ## _capture_type_1 x; scope_exit_ ## __LINE__ ## _capture_type_2 y; ... };
こんな風になっています(これも実際はもっと手がこんでいます)。外でtypedefした名前を型の中に持ち込みます。
なんとかする
C++0xではラムダ式が導入されるので、このような小細工は不要です。現行のC++で、どうしてもSCOPE_EXIT_ENDを書きたくないというのなら、次のようなコードが考えられます。
SCOPE_EXIT((x)(y), (BEGIN int a = 1, b = 2, c = 1 + 2; std::cout << "Goodbye World..." <<< std::endl; END))
関数本体を(BEGIN と END)で囲っています。http://d.hatena.ne.jp/DigitalGhost/20090805/1249478914を使って、関数本体を丸ごとマクロの引数にしてしまう、という手法です。
struct scope_exit_ ## __LINE__ ## _type { // スコープ内で一意な型名を付ける // (x)(y)から、↓のようなメンバ変数宣言を生成 BOOST_TYPEOF(外のx) x; BOOST_TYPEOF(外のy) y; unique_type( // (x)(y)から、↓のようなコンストラクタのパラメータリストを生成 BOOST_TYPEOF(外のx) x, BOOST_TYPEOF(外のy) y ) : // (x)(y)から、↓のような初期化子リストを生成 x(x), y(y) {} { // ここに関数本体を展開 } } scope_exit_ ## __LINE__ ## _obj = { // スコープ内で一意な名前を付ける // (x)(y)から、↓のようなコンストラクタ引数リストを生成 x, y };
この方法だと、SCOPE_EXIT・SCOPE_EXIT_ENDと二つに分断されていないので、キャプチャ変数リストで直接初期化できます。関数本体に書ける内容について、おそらく制約はありません(小括弧の開き閉じの数はバランスしている必要がありますが、そもそもバランスしていないと不正なコードなので、それが制約になることはないはずです)。欠点としては、(BEGIN ... END)が異常にブサイクという点です……