web-dev-qa-db-ja.com

いつ独自の例外タイプを作成する必要がありますか?

私は、エラー処理のために例外を使用している古いCコードを新しいC++に再構築するプロジェクトに取り組んでいます。

モジュールごとに異なる例外タイプを作成しています。

価値があるとは思いませんが、私の主張を証明する有効な議論はありません。そのため、標準ライブラリを作成した場合、vector_exception、list_exceptionなどが表示されます。

それについて考えながら、私はこの質問につまずいた。

いつ独自の例外タイプを作成する必要があり、いつstdライブラリで既に作成された例外に固執する必要がありますか?

また、上記のアプローチを採用した場合、近い将来に直面する可能性のある問題は何でしょうか?

24
Amit Bhaira

次の場合に独自の例外タイプを作成します。

  1. 処理するときにそれらを区別することができます。異なるタイプの場合、異なるcatch句を記述するオプションがあります。それらは適切なときに共通の処理を行えるように、依然として共通のベースを持つ必要があります
  2. ハンドラーで実際に使用できる特定の構造化データを追加したい

list例外とvector例外を別々に持つことは、それらに明確にリストのようなものやベクトル的なものがない限り、価値があるとは思えません。エラーが発生したコンテナのタイプに応じて、異なるキャッチ処理を実際に行うつもりですか?

逆に、実行時に回復できる可能性があるものと、ロールバックされているが再試行される可能性があるものと、致命的であるかバグを示しているものに対して、別々の例外タイプを持つことは理にかなっています。

25
Useless

どこでも同じ例外を使用するのは簡単です。特にその例外をキャッチしようとするとき。残念ながら、それは ポケモン例外処理 の扉を開きます。予期しない例外をキャッチするリスクをもたらします。

すべての異なるモジュールに専用の例外を使用すると、いくつかの利点が追加されます。

  • 各ケースのカスタムメッセージ。レポート作成に役立つ
  • Catchは意図した例外のみをキャプチャでき、予期しないケースでより簡単にクラッシュします(はい、不適切な処理よりも有利です)
  • クラッシュ時に、IDEは例外タイプを表示でき、デバッグをさらに支援します
10
JVApen

プログラマーがそれをサポートするのに多くの時間を費やしているため、他の答えの理由に加えてコードが読みやすくなると思います。 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()関数で出力されたユーザーへのメッセージはこのカスタム例外クラス内に隠されています。

5
stackoverflower

2つの理由が考えられます。

1)例外に関するいくつかのカスタム情報を例外クラスに保存する場合は、追加のデータメンバーを含む例外が必要です。多くの場合、std例外クラスの1つから継承します。

2)例外を区別する場合。たとえば、2つの異なるinvalid argument状況があり、catchブロックで2つの状況を区別できるようにしたいとします。 std::invalid_argumentの2つの子クラスを作成できます

2
Fabio

STLの各要素が個別のメモリ例外を発生させる場合に発生する問題は、stlの一部が他の部分に基づいて構築されているため、キューがリスト例外をキャッチして変換するか、キューのクライアントがリスト例外を処理する必要があることです。

例外を異なるモジュールから分離する際のロジックを見ることができますが、プロジェクト全体の例外ベースクラスを持つロジックも見ることができます。例外型クラスを持つロジックも確認できます。

不浄な同盟は、例外階層を構築することです。

std::exception
  EXProjectBase
    EXModuleBase
      EXModule1
      EXModule2
    EXClassBase
      EXClassMemory
      EXClassBadArg
      EXClassDisconnect
      EXClassTooSoon
      EXClassTooLate

次に、実際に発生したすべての例外は[〜#〜] both [〜#〜]モジュールと分類から派生することを主張します。次に、HighLevelDisconnectとLowLevelDisconnectを個別にキャッチするのではなく、キャッチャーにとって意味のあるもの(切断など)をキャッチできます。

一方、HighLevelインターフェースはLow​​Levelの障害を完全に処理し、HighLevel APIクライアントがこれらの障害を認識しないようにすることを提案することも完全に公平です。これは、モジュールによるキャッチが有用な最終手段です。

2
Gem Taylor

try...catchセクションについて、「このコードは、エラーから回復する方法を知っていますか?

try...catchを使用するコードは、例外が発生する可能性があると予想しています。例外を単に通過させる代わりにtry...catchat allと書く理由は次のとおりです。

  1. 親関数の例外を安全にするためのコードを追加します。これの多くはデストラクタでRAIIを使用して静かに行う必要がありますが、場合によっては(たとえば移動操作など)、部分的に完了したジョブをロールバックするためにさらに作業が必要になります。これは、必要な例外安全性のレベルによって異なります。
  2. デバッグ情報を発行または追加して、例外を再スローします。
  3. エラーから回復してみてください。

ケース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ブロックが特別に記述される場合があります。 (信頼できない外部データを読み取るときにこれを行うことがよくありますが、多くのことが可能です予測可能間違っています。)

これらの、まれなケースでは、ユーザー定義型が理にかなっています。

1
spraff