20年以上前、C++に例外処理が追加されました。長年にわたる開発者たちの経験を経て、今ではこの機能の長所と短所について貴重な知見が得られています。
C++コミュニティの著名人たちの意見を見ながら、例外の使用について彼らがどう考えているのかを探ってみましょう。
I. C++の専門家
Herb Sutter は、自身の記事で次のように述べています。
要約すると、エラーを通知するにはエラーコードよりも例外を使う方が望ましいということです。例外を使用できない場合(呼び出し側のコードをすべて制御できず、それがC++で書かれ、同じコンパイラと互換性のあるコンパイルオプションでビルドされるため例外処理が正しく機能すると保証できない場合)や、エラーではない状態に対してのみ、ステータスコード(リターンコードや errno など)を使用します。回復が不要または不可能な場合には、正常終了または異常終了など、ほかの手段を使用します。
Bjarne Stroustrup は、自身のFAQページで次のように述べています。
例外を使うと私にどんな利点があるのでしょうか。基本的な答えは、エラー処理に例外を使うことで、コードがよりシンプルでクリーンになり、エラーの見落としも減るということです。では、昔ながらの errno と if 文の何が問題なのでしょうか。基本的な答えは、それらを使うとエラー処理と通常のコードが密接に絡み合ってしまうということです。その結果、コードは読みにくくなり、すべてのエラーに対処できているかを確認するのが難しくなります(「スパゲッティコード」や「テストの絡まり合った巣」を想像してください)。
Scott Meyers は、著書『Effective C++』で次のように述べています。
40年前、goto を多用したコードは完全に良い習慣と見なされていました。今では、構造化された制御フローを書こうと努めています。20年前、グローバルにアクセス可能なデータは完全に良い習慣と見なされていました。今では、データをカプセル化しようと努めています。10年前、例外の影響を考えずに関数を書くことは良い習慣と見なされていました。今では、例外安全なコードを書こうと努めています。
Joel Spolsky は、自身の記事で次のように述べています。
なぜ私が例外を使ったプログラミングを好まないのかと尋ねる人がいます。JavaでもC++でも、私の方針は次のとおりです。
1. 自分から例外をスローしない。2. 使用しているライブラリがスローする可能性のある例外は、必ずスローされたのと同じ行ですべて捕捉し、直ちに処理する。
Andrei Alexandrescu は、自身の記事で次のように述べています。
例外安全なコードを書くのは難しい
同じ記事の後半で、彼はスコープガード・イディオムを使うことで例外の利用をどう単純化できるかを説明しています。
ScopeGuard は、C++の典型的なイディオムである「初期化はリソースの取得である」の一般化です。違いは、ScopeGuard がクリーンアップ部分だけに焦点を当てている点です。リソースの取得は自分で行い、ScopeGuard がリソースの解放を引き受けます。(実際、クリーンアップこそこのイディオムで最も重要な部分だと言えるでしょう。)
Jon Kalb は、自身のWebサイトで次のように述べています。
例外を安全に使用することは、この20年間の大半にわたって業界が取り組んできた、容易ではない課題です。例外安全性に不安や疑問を感じている方、あるいはC++で例外を使う際のベストプラクティスを知りたい方には、このセッションが適しています。まず「解決しようとしている問題は何か」から始め、代替案を検討し、例外の使用に伴う課題を確認し、善意から生まれたものの誤った安全策についても触れます。そのうえで、例外を安全に使用するための指針となるガイドライン一式と、例外安全でないレガシーコードベースからの移行方法を含む、堅牢な実装技術を紹介します。
II. 実際のプロジェクト
Boost スタイルガイドより:
例外はいつ使うべきか?
簡単な答えは、「例外の意味論とパフォーマンス特性が適切である場合には、いつでも」です。
Google スタイルガイドより
表面的には、特に新規プロジェクトでは、例外を使う利点がコストを上回ります。しかし既存コードの場合、例外の導入は依存するすべてのコードに影響します。例外が新しいプロジェクトの外へ伝播する可能性がある場合、そのプロジェクトを例外を使わない既存コードへ統合するのも難しくなります。Googleの既存C++コードの大半は例外を扱う準備ができていないため、例外を生成する新しいコードを採用するのは比較的困難です。
Clang
ClangチームはC++例外を使用しないことを決定しました。彼らのコーディング規約からの引用は次のとおりです。
コードと実行ファイルのサイズを削減するため、LLVMではRTTI(例:dynamic_cast<>)や例外を使用していません。これら2つの言語機能は、「使ったものに対してのみコストを支払う」というC++の一般原則に反しており、コードベースで例外が一度も使われなくても、あるいはRTTIがあるクラスで一度も使われなくても、実行ファイルを肥大化させます。このため、私たちはコード全体でこれらを無効にしています。
一方、LibreOffice はC++例外を使用しています。
有名なオープンソースのC++プロジェクトを多数見ても、明確な優位はありません。どちらのアプローチも広く使われています。
III. C++コミュニティ
フォーラム、記事、ソーシャルメディア上の議論を見ると、C++コミュニティ内には主に3つの見解があることが分かります。
- 使うべき――例外的な状況に対処する最良の方法だから。
- 使うべきだが、十分に注意すべき――副作用があるから。
- 絶対に使うべきではない。
C++開発者として、私は非常に戸惑っています。使うべきなのでしょうか、それとも使わないべきなのでしょうか。
これらのC++専門家が共通して指摘している大きな問題が1つあります。例外安全なコードを書くのは容易ではないということです。C++開発者は4つのカテゴリに分類できます。
カテゴリ1:例外安全性を気にせずにC++例外を使用する。
カテゴリ2:例外安全性の問題を認識したうえでC++例外を使用する。
カテゴリ3:例外を使用しないが、その長所と短所を十分に理解していない。
カテゴリ4:例外の欠点を十分に理解しているため使用しない。
カテゴリ2と4の開発者は、自分が何をしているのかを正確に理解しています。一方、カテゴリ1と3の開発者は例外の仕組みを十分に理解しておらず、それがコードの品質に影響する可能性があります。例外を使うべきかどうかを議論すること自体が、もしかすると間違った問いなのかもしれません。より重要なのは、例外を使う長所と短所を理解しているかどうかです。あとは、それぞれの制約に基づいて選択すればよいのです。どちらのアプローチを選んでも、誰にも責められることではありません。
