私にとって例外処理は、すべてがRAIIに準拠し、本当に例外的なパス(例:破損したファイルが読み取られる)の例外を予約していて、モジュールの境界を越えてチーム全体がそれらに参加している場合に最適です。 ……
ゼロコストEH
最近のほとんどのコンパイラーはゼロコストの例外処理を実装しているため、通常の実行パスでエラー条件を手動で分岐するよりもさらに安価にできますが、それと引き換えに例外パスは非常に高価になります(コードの肥大化もありますが、おそらくそれ以上ではありません)考えられるすべてのエラーを手動で完全に処理した場合よりも)。例外がC ++で意図された方法で使用されている場合、スローが非常に高価であるという事実は問題になりません(通常の状況では発生しないはずの本当に例外的な状況の場合)。
副作用の逆転
そうは言っても、すべてをRAIIに準拠させるのは言うよりもはるかに簡単です。関数に格納されているローカルリソースを、それらをクリーンアップするデストラクタでC ++オブジェクトにラップするのは簡単ですが、ソフトウェア全体で発生する可能性のあるすべての副作用の元に戻す/ロールバックロジックを記述するのは簡単ではありません。std::vector
完全に例外セーフにするための最も単純なランダムアクセスシーケンスのようなコンテナを書くのがどれほど難しいかを考えてみてください。次に、大規模なソフトウェア全体のすべてのデータ構造にわたってその難易度を乗算します。
基本的な例として、シーングラフにアイテムを挿入するプロセスで発生した例外を考えます。その例外から適切に回復するには、シーングラフでのこれらの変更を元に戻し、操作が発生していないかのようにシステムを状態に戻す必要がある場合があります。つまり、おそらくスコープガードから、例外が発生したときにシーングラフに挿入された子を自動的に削除します。
多くの副作用を引き起こし、多くの永続的な状態を処理するソフトウェアでこれを徹底的に行うことは、言うよりもはるかに簡単です。多くの場合、実際的な解決策は、物事を出荷するためにそれを気にしないことです。ある種の中央undoシステムを備えた適切な種類のコードベースと、おそらく永続的なデータ構造と副作用を引き起こす最小限の関数を使用すると、全面的に例外安全性を達成できる可能性があります...しかし、それはたくさんありますあなたがやろうとしていることがコードをどこでも例外的に安全にしようとしているなら、あなたが必要とするもの。
概念的には副作用の逆転は難しい問題ですが、C ++ではさらに難しくなるため、実際には何か問題があります。実際には、C ++の例外に対応するよりも、エラーコードに対応するCの副作用を元に戻す方が簡単な場合があります。理由の1つは、関数の特定のポイントがthrow
C ++で潜在的に発生する可能性があるためです。type T
で動作する一般的なコンテナーを作成しようとしている場合、関連するものT
がスローされる可能性があるため、これらの出口点がどこにあるかも予測できません。T
等しいかどうかを比較することでもスローする可能性があります。コードではあらゆる可能性を処理する必要があり、C ++では可能性の数が指数関数的に増加しますが、Cでは可能性がはるかに少なくなります。
標準の欠如
例外処理のもう1つの問題は、中央標準の欠如です。うまくいけば、ロールバックが失敗することはないので、デストラクタがスローするべきではないことに誰もが同意することを望んでいる人もいますが、これは、ルールに違反した場合にソフトウェアを通常クラッシュさせる病理学的ケースをカバーしています。
比較演算子から決してスローしない(論理的に不変であるすべての関数は決してスローしてはならない)、ムーブクターからスローしないなど、より適切な標準が必要です。このような保証により、例外セーフなコードを非常に簡単に記述できます。ただし、そのような保証はありません。私たちは、すべてがスローする可能性があるルールに従う必要があります-今ではないとしても、将来的には。
さらに悪いことに、言語の背景が異なる人々は例外を非常に異なって見ます。Pythonでは、彼らは実際に、通常の実行パスで例外をトリガーすることを含む、この「見る前に飛躍する」哲学を持っています!次に、そのような開発者にC ++コードの記述を依頼すると、実際には例外的でないものに対して左右にスローされる例外を確認できます。一般的なコードを記述しようとすると、そのすべてを処理することは悪夢になる可能性があります。
エラー処理
エラー処理には、発生する可能性のあるすべてのエラーをチェックする必要があるという欠点があります。ただしglGetError
、関数の出口点を大幅に減らすだけでなく、明示的にするために、後から少し遅れてエラーをチェックできる場合があるという利点もあります。エラーをチェックして伝播および回復する前に、コードを少し長く実行し続けることができる場合もあれば、例外処理よりも本当に簡単な場合もあります(特に副作用の逆転の場合)。しかし、ゲームのエラーもそれほど気にしないかもしれません。おそらく、破損したファイルやメモリ不足エラーなどの問題が発生した場合は、メッセージでゲームをシャットダウンするだけです。
複数のプロジェクトで使用されるライブラリの開発との関係。
DLLコンテキストでの例外の厄介な部分は、同じコンパイラーでビルドされていること、同じ設定を使用していることが保証されていない限り、あるモジュールから別のモジュールに例外を安全にスローできないことです。したがって、プラグインアーキテクチャのように記述している場合あらゆる種類のコンパイラ、そしておそらくLua、C、C#、Javaなどの異なる言語から使用される人々を対象としていますが、すべての例外を飲み込んでエラーに変換する必要があるため、例外が大きな迷惑になります。とにかくどこでもコード。
サードパーティのライブラリを使用するとどうなりますか。
dylibの場合、上記の理由により例外を安全にスローできません。彼らはエラーコードを使用する必要があります。つまり、ライブラリを使用すると、エラーコードを常にチェックする必要があります。静的にリンクされたC ++ラッパーライブラリでdylibをラップし、dylibからのエラーコードをスローされた例外(基本的にはバイナリからバイナリにスロー)に変換することができます。一般的に、dylibs /共有ライブラリを使用する場合、ほとんどのサードパーティのライブラリは煩わしくなく、エラーコードだけに固執するべきです。
単体テスト、統合テストなどにどのように影響しますか?
私は通常、コードの例外/エラーパスをテストするチームにそれほど遭遇しません。私はそれを使用していましたが、実際にコードをbad_alloc
超堅牢にしたかったので適切に回復できるコードがありましたが、チームでそれを実行しているのが私だけの場合はあまり役に立ちません。結局私は面倒をやめた。チーム全体がコードを確実に例外やエラーから確実に回復できるようにするためのミッションクリティカルなソフトウェアを想像しますが、それは投資するのに長い時間です。時間はミッションクリティカルなソフトウェアでは理にかなっていますが、ゲームではないかもしれません。
愛憎
例外も私のC ++の愛/嫌いな機能の1つです。たとえば、コードを逆さまに記述する必要がある方法を変更するため、RAIIを利便性というよりも要件のようにする、言語の最もインパクトのある機能の1つです。 、Cは、特定のコード行が関数の暗黙の出口点になる可能性があるという事実を処理します。ときどき、言語に例外がないことを望みます。しかし、例外が本当に役立つと感じる場合もありますが、CとC ++のどちらでコードを書くかを選択する際に、例外が多すぎるためです。
標準委員会が、少なくともC ++からC ++コードまで、モジュール間で安全に例外をスローできるようにするABI標準の確立に重点を置いている場合、それらは指数関数的に私にとってより有用です。安全に複数の言語を使い分けられるとしたら、それはすばらしいことですが、それは一種の夢です。例外が指数関数的にさらに便利になるもう1つの理由は、例外noexcept
が発生したときに呼び出すのではなく、スローできる機能を呼び出そうとしたときに、関数が実際にコンパイラエラーを引き起こした場合std::terminate
です。これは、役に立たない(のicantexcept
代わりにと呼ぶこともありますnoexcept
)の隣にあります。たとえば、次のような場合、すべてのデストラクタが例外に対して安全であることを確認する方がはるかに簡単です。noexcept
デストラクタがスローできるものを実行すると、実際にはコンパイラエラーが発生しました。それがコンパイル時に完全に決定するにはコストが高すぎる場合は、noexcept
関数(すべてのデストラクタが暗黙的にそうでなければならない)が同様にnoexcept
関数/演算子を呼び出すことだけが許可され、コンパイラエラーがそれらのいずれかからスローされるようにするだけです。