私は、エラー処理のために例外を使用している古いCコードを新しいC++に再構築するプロジェクトに取り組んでいます。
モジュールごとに異なる例外タイプを作成しています。
価値があるとは思いませんが、私の主張を証明する有効な議論はありません。そのため、標準ライブラリを作成した場合、vector_exception、list_exceptionなどが表示されます。
それについて考えながら、私はこの質問につまずいた。
いつ独自の例外タイプを作成する必要があり、いつstdライブラリで既に作成された例外に固執する必要がありますか?
また、上記のアプローチを採用した場合、近い将来に直面する可能性のある問題は何でしょうか?
次の場合に独自の例外タイプを作成します。
catch
句を記述するオプションがあります。それらは適切なときに共通の処理を行えるように、依然として共通のベースを持つ必要がありますlist
例外とvector
例外を別々に持つことは、それらに明確にリストのようなものやベクトル的なものがない限り、価値があるとは思えません。エラーが発生したコンテナのタイプに応じて、異なるキャッチ処理を実際に行うつもりですか?
逆に、実行時に回復できる可能性があるものと、ロールバックされているが再試行される可能性があるものと、致命的であるかバグを示しているものに対して、別々の例外タイプを持つことは理にかなっています。
どこでも同じ例外を使用するのは簡単です。特にその例外をキャッチしようとするとき。残念ながら、それは ポケモン例外処理 の扉を開きます。予期しない例外をキャッチするリスクをもたらします。
すべての異なるモジュールに専用の例外を使用すると、いくつかの利点が追加されます。
プログラマーがそれをサポートするのに多くの時間を費やしているため、他の答えの理由に加えてコードが読みやすくなると思います。 2つのコードを検討します(「空のフレーム」エラーがスローされると仮定):
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw std::exception("Error: empty frame");
}
}
void MyClass::function() noexcept(false) {
// ...
if (errorCondition) {
throw EmptyFrame();
}
}
2番目のケースでは、より読みやすく、what()関数で出力されたユーザーへのメッセージはこのカスタム例外クラス内に隠されています。
2つの理由が考えられます。
1)例外に関するいくつかのカスタム情報を例外クラスに保存する場合は、追加のデータメンバーを含む例外が必要です。多くの場合、std例外クラスの1つから継承します。
2)例外を区別する場合。たとえば、2つの異なるinvalid argument
状況があり、catch
ブロックで2つの状況を区別できるようにしたいとします。 std::invalid_argument
の2つの子クラスを作成できます
STLの各要素が個別のメモリ例外を発生させる場合に発生する問題は、stlの一部が他の部分に基づいて構築されているため、キューがリスト例外をキャッチして変換するか、キューのクライアントがリスト例外を処理する必要があることです。
例外を異なるモジュールから分離する際のロジックを見ることができますが、プロジェクト全体の例外ベースクラスを持つロジックも見ることができます。例外型クラスを持つロジックも確認できます。
不浄な同盟は、例外階層を構築することです。
std::exception
EXProjectBase
EXModuleBase
EXModule1
EXModule2
EXClassBase
EXClassMemory
EXClassBadArg
EXClassDisconnect
EXClassTooSoon
EXClassTooLate
次に、実際に発生したすべての例外は[〜#〜] both [〜#〜]モジュールと分類から派生することを主張します。次に、HighLevelDisconnectとLowLevelDisconnectを個別にキャッチするのではなく、キャッチャーにとって意味のあるもの(切断など)をキャッチできます。
一方、HighLevelインターフェースはLowLevelの障害を完全に処理し、HighLevel APIクライアントがこれらの障害を認識しないようにすることを提案することも完全に公平です。これは、モジュールによるキャッチが有用な最終手段です。
try...catch
セクションについて、「このコードは、エラーから回復する方法を知っていますか?
try...catch
を使用するコードは、例外が発生する可能性があると予想しています。例外を単に通過させる代わりにtry...catch
at allと書く理由は次のとおりです。
ケース1と2の場合、おそらくユーザー例外タイプを心配する必要はありません。これらは次のようになります。
try {
....
} catch (const std::exception & e) {
// print or tidy up or whatever
throw;
} catch (...) {
// print or tidy up or whatever
// also complain that std::exception should have been thrown
throw;
}
私の経験では、これはおそらく現実世界のケースの99%になります。
エラーから回復したい場合があります。おそらく、親関数は特定の条件でライブラリが失敗することを知っています動的に修正可能、または異なるメカニズムを使用してジョブを再試行する戦略があります。
下位レベルのコードが通常のフローの一部として例外をスローするに設計されているため、catch
ブロックが特別に記述される場合があります。 (信頼できない外部データを読み取るときにこれを行うことがよくありますが、多くのことが可能です予測可能間違っています。)
これらの、まれなケースでは、ユーザー定義型が理にかなっています。