C++とfinally
こんにちは、株式会社CFlatです。
あれほど何でも取り込むC++がC++11になってもいまだに採用しない言語仕様で、他の言語にはよく採用されているもの。
幾つかありますが、代表的なものがGC(ガベージコレクション)とfinallyブロックでしょう。
ただ今回は、GCについてはTechnical Reportに既に出ているようですし、Boehm GCというライブラリ(c.f. http://homepage2.nifty.com/aito/gc/gc.html)もありますので割愛します。ちょっとfinallyについて見ていきましょう。
finally文の使い方
finally文は、例えば以下のように使われます。
try { std::cout << "例外を投げるかもしれない処理" << std::endl ; return ; } catch (...) { std::cout << "例外が発生した!" << std::endl ; throw ; } finally { std::cout << "try{}が終わろうともreturnされようともthrowされようとも必ず実行する処理" << std::endl ; }
もしくは、こう。
try { std::cout << "例外を投げるかもしれない処理" << std::endl ; return ; } finally { std::cout << "try{}が終わろうともreturnされようともthrowされようとも必ず実行する処理" << std::endl ; }
後者の場合、例外はtry〜finallyの外に再スローされますが、その際にfinallyブロックの中身が実行されます。
これの何が嬉しいかって、次のコードを見てみれば一目瞭然です:
int *p = new int[10] ; try { std::cout << "例外を投げるかもしれない処理" << std::endl ; if (condition) { delete[] p ; return ; } if (need_goto) { delete[] p ; goto LABEL ; } ... delete[] p ; } catch (std::exception e) { delete[] p ; std::cout << "Caught a std::exception!" << std::endl ; } catch (...) { delete[] p ; std::cout << "Caught an unexpected exception!" << std::endl ; throw ; } ... LABEL : ...
tryから抜ける処理の前には、必ず全てdeleteをしなければならない。極めて面倒ですね。
finallyを使えばdeleteを全て一箇所に纏め、次のようにすっきりと書けるはずです。
int *p = new int[10] ; try { std::cout << "例外を投げるかもしれない処理" << std::endl ; if (condition) return ; if (need_goto) goto LABEL ; ... } catch (std::exception e) { std::cout << "Caught a std::exception!" << std::endl ; } catch (...) { std::cout << "Caught an unexpected exception!" << std::endl ; throw ; } finally { delete[] p ; } ... LABEL : ...
C++がfinallyを採用しない理由
C++がこの便利なfinallyを採用しないのは、何故でしょうか?
C++の開発者であるBjarne Stroustrupは「C++は代替手段を提供しており、それは大抵の場合で(finallyと比べて)より良い(C++ supports an alternative that is almost always better)」と述べています(http://www.stroustrup.com/bs_faq2.html#finally)。
そんなわけで上のコードを、その代替手段とやらを使って書き直してみます。
try { std::unique_ptr<int[]> p(new int[10]) ; std::cout << "例外を投げるかもしれない処理" << std::endl ; if (condition) return ; if (need_goto) goto LABEL ; ... } catch (std::exception e) { std::cout << "Caught a std::exception!" << std::endl ; } catch (...) { std::cout << "Caught an unexpected exception!" << std::endl ; throw ; } ... LABEL : ...
ブロックから抜ける際、抜ける方法が何であれ必ずpのデストラクタが呼ばれるので、finallyの代わりになる、という寸法。
ただ、今回はnewしたポインタなのでstd::unique_ptrが使えましたが、それ以外のリソースに対しては、自分でラッパークラスを用意してやらねばなりません。それでもBjarneは「システムの全体を見れば、同じ種類のリソースをいろんな場所で使うことになるのだから、全ての場所でfinallyするよりはひとつクラスを作っておいた方が楽だろ?」と主張しています。
他にも、デストラクタのほうが優れている理由としては、「ムーブセマンティクスを活用すれば、後始末のタイミングをリソース生成とは別のスコープで行える」やら「finallyのように、間の処理が長くなると初期化処理と後始末がコード上で離れていき、わけがわからなくなる心配がない」もあるかと思います。
とはいえ、必ずしも全てのfinallyをクラスで置き換えたいわけではないのも事実。あまり多用されても困るので具体的なコードは書きませんが、ラムダを受け取ってデストラクタで実行する疑似finallyクラスを作ればよいのではないでしょうか?