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

C++とfinally

C++

こんにちは、株式会社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クラスを作ればよいのではないでしょうか?