web-dev-qa-db-ja.com

std :: exceptionを継承する必要がありますか?

少なくとも1つの信頼できるソース(私が取ったC++クラス)で、C++のアプリケーション固有の例外クラスは_std::exception_を継承することを推奨しています。このアプローチの利点については明確ではありません。

C#では、ApplicationExceptionから継承する理由は明らかです。いくつかの便利なメソッド、プロパティ、およびコンストラクターを取得し、必要なものを追加またはオーバーライドするだけです。 _std::exception_を使用すると、オーバーライドするwhat()メソッドだけが得られるように見えます。

それでは、_std::exception_をアプリケーション固有の例外クラスの基本クラスとして使用するメリットはありますか? _std::exception_から継承しない理由はありますか?

96
John M Gant

主な利点は、クラスを使用するコードは、throwの正確なタイプを知る必要はなく、catch the std::exceptionだけを把握できることです。

編集: Martinや他の人が述べたように、実際にはstd::exceptionヘッダーで宣言された<stdexcept>のサブクラスの1つから派生させたいと思います。

68

std::exceptionの問題は、メッセージを受け入れるコンストラクター(標準準拠バージョン)がないことです。

その結果、std::runtime_errorから派生することを好みます。これはstd::exceptionから派生しますが、そのコンストラクターにより、C-Stringまたはstd::stringwhat()のときに(char const*として)返されるコンストラクターに渡すことができます。と呼ばれます。

39
Martin York

std::exceptionから継承する理由は、例外の「標準」基本クラスであるため、たとえば、それを期待して基本std::exceptionをキャッチすることは、チームの他の人にとっては自然なことです。

利便性を求めている場合は、std::runtime_errorコンストラクターを提供するstd::stringから継承できます。

17
Aleksei Potov

以前の作者がint、HRESULTS、std :: string、char *、ランダムクラスなどを投げていた大きなコードベースのクリーンアップに参加したことがあります。型に名前を付けるだけで、おそらくどこかにスローされました。そして、共通の基本クラスはまったくありません。信じられますが、投げられたすべてのタイプには共通の基盤があり、何も通り抜けられないことがわかっているという点に到達すると、物事はずっときれいになりました。したがって、自分自身(および将来コードを保守する必要のある人)にお願いし、最初からそのようにしてください。

12
timday

boost :: exception を継承する必要があります。それは多くの機能と追加のデータを運ぶためのよく知られた方法を提供します...もちろん、 Boost を使用していない場合は、この提案を無視してください。

10
James Schek

はい、あなたはstd::exception

他の人は、std::exceptionには、テキストメッセージを渡すことができないという問題がありますが、通常、スローの時点でユーザーメッセージをフォーマットしようとすることはお勧めできません。代わりに、例外オブジェクトを使用して、すべての関連情報をキャッチサイトに転送し、キャッチサイトでユーザーフレンドリーなメッセージをフォーマットできます。

9
Emil

std::exceptionから継承する理由は、そのクラスに従ってキャッチされた例外をスローできるためです。

class myException : public std::exception { ... };
try {
    ...
    throw myException();
}
catch (std::exception &theException) {
    ...
}

継承には、オブジェクトのスライシングについて知っておくべき問題が1つあります。 throw e; throw-expressionは、例外オブジェクトと呼ばれる一時オブジェクトを初期化します。例外オブジェクトのタイプは、throwのオペランドの静的タイプからトップレベルのcv修飾子を削除することによって決定されます。それはあなたが期待していることではないかもしれません。あなたが見つけることができる問題の例 here

それは継承に対する議論ではなく、単に「知っておくべき」情報です。

違い:std :: runtime_error vs std :: exception()

あなたがshouldから継承するかどうかはあなた次第です。標準std::exceptionとその標準の子孫は、1つの可能な例外階層構造を提案します(logic_errorサブ階層とruntime_errorサブ階層)および1つの可能な例外オブジェクトインターフェイス。あなたがそれを好めば-それを使用してください。何らかの理由で別の何かが必要な場合-独自の例外フレームワークを定義します。

5
AnT

標準の例外タイプから派生するかどうかが最初の質問です。そうすることで、すべての標準ライブラリ例外と独自の例外に対して単一の例外ハンドラーが有効になりますが、そのようなすべてをキャッチするハンドラーも推奨されます。問題は、処理方法がわかっている例外のみをキャッチする必要があるということです。たとえば、main()では、終了する前にwhat()文字列が最後の手段としてログに記録される場合、すべてのstd :: exceptionsをキャッチするのが適切です。ただし、他の場所では、良いアイデアとは言えません。

標準の例外タイプから派生するかどうかを決定したら、どちらがベースになるかが問題になります。アプリケーションがi18nを必要としない場合、呼び出しサイトでメッセージをフォーマットすることは、情報を保存して呼び出しサイトでメッセージを生成するのと同じくらい良いと考えるかもしれません。問題は、フォーマットされたメッセージが必要ない場合があることです。遅延メッセージ生成スキームを使用する方がよい-おそらく事前に割り当てられたメモリで。次に、メッセージが必要な場合、アクセス時に生成されます(場合によっては、例外オブジェクトにキャッシュされます)。したがって、スローされたときにメッセージが生成される場合、std :: runtime_errorのようなstd :: exception派生が基本クラスとして必要です。メッセージが遅延生成される場合、std :: exceptionが適切なベースです。

3
Rob

考えられるすべての例外が_std::exception_から派生している場合、catchブロックは単にcatch(std::exception & e)であり、すべてを確実にキャプチャできます。

例外をキャプチャしたら、そのwhatメソッドを使用して詳細情報を取得できます。 C++はダックタイピングをサポートしていないため、whatメソッドを持つ別のクラスは、それを使用するために異なるキャッチと異なるコードを必要とします。

3
Mark Ransom

この言語はすでにstd :: exceptionをスローしているため、適切なエラー報告を提供するには、とにかくキャッチする必要があります。独自のすべての予期しない例外にも同じキャッチを使用できます。また、例外をスローするほとんどすべてのライブラリは、例外をstd :: exceptionから派生させます。

言い換えれば、そのどちらか

catch (...) {cout << "Unknown exception"; }

または

catch (const std::exception &e) { cout << "unexpected exception " << e.what();}

そして、2番目のオプションは間違いなく優れています。

3
Arkadiy

サブクラス例外のもう1つの理由は、カプセル化された大規模なシステムで作業する場合の設計面の改善です。検証メッセージ、ユーザークエリ、致命的なコントローラーエラーなどに再利用できます。メッセージのようにすべての検証を書き換えたり、再フックしたりするのではなく、メインソースファイルで単純に「キャッチ」できますが、クラスセット全体のどこにでもエラーをスローできます。

例えば致命的な例外はプログラムを終了し、検証エラーはスタックのみをクリアし、ユーザークエリはエンドユーザーに質問をします。

この方法で行うことは、同じクラスを異なるインターフェースで再利用できることも意味します。例えばWindowsアプリケーションはメッセージボックスを使用でき、Webサービスはhtmlを表示し、レポートシステムはログを記録します。

0
James Burnby

この質問はかなり古く、既に十分に回答されていますが、C++ 11で適切な例外処理を行う方法に関するメモを追加したいと思います。

std::nested_exception および std::throw_with_nested を使用します

StackOverflow here および here で説明されており、どのようにすれば例外のバックトレースを取得できますかネストされた例外を再スローする適切な例外ハンドラーを記述するだけで、デバッガーや面倒なロギングを必要とせずにコード内で実行できます。

派生した例外クラスでこれを行うことができるため、このようなバックトレースに多くの情報を追加できます!また、私の GitHubのMWE を見ると、バックトレースは次のようになります。

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

例外がスローされたときに十分な情報を取得するために、std::runtime_errorをサブクラス化する必要さえありません。

サブクラス化で(std::runtime_errorを使用するだけでなく)唯一の利点は、例外ハンドラーがカスタム例外をキャッチして特別なことを実行できることです。例えば:

try
{
  // something that may throw
}
catch( const MyException & ex )
{
  // do something specialized with the
  // additional info inside MyException
}
catch( const std::exception & ex )
{
  std::cerr << ex.what() << std::endl;
}
catch( ... )
{
  std::cerr << "unknown exception!" << std::endl;
}
0
GPMueller