C++で例外を使ったプログラムをうまく書くにはどうすればいいのか

 C++とはできるだけ離れた人生を送りたいと願ってきた。しかし、なかなかそうもいかず、最近はだんだんとC++の知識を学びつつある。今更C++を勉強するなんてちょっと出遅れすぎてるんじゃないかという気もするが、もうすぐ新規格であるC++0xもリリースされるはずであるし、意外とC++を学ぶにはいいタイミングかもしれない。
 プログラムの品質はエラー処理をどこまで丁寧に書くかで決まる。エラー処理の書き方には、

  • エラーコードを返す(エラーコードを返せるように設計しておく)
  • エラー時に例外を投げる

 の2種類があるが、今悩んでいるのはそのC++の例外についてである。
 例えば、機械学習のツールを書くことを考える。このようなツールではデータをパースする部分を書く必要が出てくるだろう。1行に1つのデータが以下のような形式で書かれているものをパースしたい。

1 2:1 4:2 5:1

 このデータをパースするコード全体は、エラー処理をしっかり書くとなかなか長くなるので、コード全体を載せる代わりに悩んでいる問題について簡単に説明する。このデータ形式では、4:2の2の部分のように整数値が期待されているところがたくさんあり、それらに関しては、パースして即整数型に変換してしまいたい。文字列と数値の変換なのでboost::lexical_castを使うのが良いと思うのだが、この関数はエラー時にbad_lexical_castという例外を投げる。
 この例外はlexical_castが投げるものなので、このライブラリの使用者にしてみたら、「いや、そんなエラーをそのまま投げずにもっとわかり易いエラーメッセージを返してくれよ」と文句の一つも言いたくなるところだろう。そこで、何行目の何文字目でパースに失敗したとか、そういう情報をエラーメッセージに入れた独自の例外を作って、ライブラリの外に例外を投げる場合には親切な例外を投げることを考える。イメージとしては以下のような感じである。

try {
   parse_line(line, &result);
} catch (bad_lexical_cast e) {
  ostringstream oss;
  oss << "error: expected integer value: " << line_num << ":" << char_num;
  throw oss.str();
}

 さて、ここで問題になるのが、line_numとchar_numをどこから入手するかである。以下のような方法が考えられると思う。

  1. クラスのメンバー変数にする
  2. parse_lineの引数にline_num, char_numを参照渡しにする
  3. そもそもparse_lineの中でtry-catchを仕掛ける

 1.はとりあえず書いてみただけで、無駄にスレッドセーフでなくなるので論外である。そもそも、クラスを使っていないかもしれないしね。2は特段問題はないのだが、なんだか説明のしようがない気持ち悪さがある。エラーコードと例外の折衷案的な感じというか。3.は一番きれいだと思うのだが、parse_lineを呼ぶたびにtryを呼ぶのって、速度重視のC++においてはどうなんだろう。
 この例題の場合は、1行あたり1回tryを呼ぶのはパフォーマンス的には問題にならないと思うので、仕事で書くなら3.を選ぶと思うのだが、parse_lineに相当する関数がもっと軽くて頻繁に呼ばれる場合は、3.だとパフォーマンスが問題になるかもしれない。そういう場合には2.を選ぶべきなのだろうか…とか考え出すともやもやする。
 今のところ、自分としては決定的な答えは得られていないのだが、C++erにしてみれば「それは15年ぐらい前に俺達が通った道だ!」という感じなんでしょうか…。