web-dev-qa-db-ja.com

C ++で例外指定子を使用する必要がありますか?

C++では、例外指定子を使用して、関数が例外をスローするかどうかを指定できます。例えば:

void foo() throw(); // guaranteed not to throw an exception
void bar() throw(int); // may throw an exception of type int
void baz() throw(...); // may throw an exception of some unspecified type

次の理由で、実際にそれらを使用するのは疑わしいです。

  1. コンパイラーは、厳密な方法で例外指定子を実際に強制しないため、利点は大きくありません。理想的には、コンパイルエラーを取得したいと考えています。
  2. 関数が例外指定子に違反する場合、標準的な動作はプログラムを終了することだと思います。
  3. VS.Netでは、throw(X)をthrow(...)として扱うため、標準への準拠は強くありません。

例外指定子を使用すべきだと思いますか?
「はい」または「いいえ」で答え、答えを正当化する理由をいくつか入力してください。

119

番号。

理由の例をいくつか示します。

  1. テンプレートコードは例外仕様を使用して記述することはできません。

    _template<class T>
    void f( T k )
    {
         T x( k );
         x.x();
    }
    _

    コピーがスローされ、パラメーターの受け渡しがスローされ、x()が不明な例外をスローする場合があります。

  2. 例外仕様は拡張性を禁止する傾向があります。

    _virtual void open() throw( FileNotFound );
    _

    に進化するかもしれない

    _virtual void open() throw( FileNotFound, SocketNotReady, InterprocessObjectNotImplemented, HardwareUnresponsive );
    _

    あなたは本当にそれを次のように書くことができます

    _throw( ... )
    _

    仮想関数を作成するとき、最初は拡張可能ではなく、2番目は野心的で、3番目は本当にあなたが意味するものです。

  3. レガシーコード

    別のライブラリに依存するコードを作成する場合、何かがひどく間違った場合にどうなるかはわかりません。

    _int lib_f();
    
    void g() throw( k_too_small_exception )
    { 
       int k = lib_f();
       if( k < 0 ) throw k_too_small_exception();
    }
    _

    lib_f()がスローされると、gは終了します。これは(ほとんどの場合)本当に必要なものではありません。 std::terminate()を呼び出さないでください。黙って/激しく死ぬよりも、スタックトレースを取得できる未処理の例外でアプリケーションをクラッシュさせる方が常に優れています。

  4. 一般的なエラーを返し、例外的な場合にスローするコードを記述します。

    _Error e = open( "bla.txt" );
    if( e == FileNotFound )
        MessageUser( "File bla.txt not found" );
    if( e == AccessDenied )
        MessageUser( "Failed to open bla.txt, because we don't have read rights ..." );
    if( e != Success )
        MessageUser( "Failed due to some other error, error code = " + itoa( e ) );
    
    try
    {
       std::vector<TObj> k( 1000 );
       // ...
    }
    catch( const bad_alloc& b )
    { 
       MessageUser( "out of memory, exiting process" );
       throw;
    }
    _

それでも、ライブラリが独自の例外をスローするだけの場合、例外仕様を使用して意図を述べることができます。

93
Christopher

C++の例外仕様を避けてください。質問であなたが述べた理由は、その理由のかなり良い出発点です。

Herb Sutterの 「例外仕様の実用的な見方」 を参照してください。

42
Michael Burr

私は、標準的に例外の規則を考えます(C++の場合)
例外指定子は、ほとんど失敗したC++標準の実験でした。
。ハーブサッターにはこのテーマに関するページがあります。 Gotch 82

さらに、例外保証について説明する価値があると思います。

これらは基本的に、オブジェクトのメソッドが例外をエスケープすることによってオブジェクトの状態がどのように影響を受けるかについてのドキュメントです。残念ながら、コンパイラーによって強制されたり、言及されたりすることはありません。
ブーストと例外

例外保証

保障はありません:

例外がメソッドをエスケープした後のオブジェクトの状態についての保証はありません
これらの状況では、オブジェクトを使用しないでください。

基本的な保証:

ほとんどすべての状況で、これはメソッドが提供する最小の保証です。
これにより、オブジェクトの状態が適切に定義され、引き続き一貫して使用できることが保証されます。

強力な保証:(別名トランザクション保証)

これにより、メソッドが正常に完了することが保証されます
または例外がスローされ、オブジェクトの状態は変更されません。

スロー保証なし:

このメソッドは、例外がメソッドから伝播できないことを保証します。
すべてのデストラクタがこの保証を行う必要があります。
| N.B.例外が既に伝播している間に例外がデストラクタをエスケープする場合
|アプリケーションは終了します

14
Martin York

gccは、例外仕様に違反すると警告を発します。私がやっていることは、「lint」モードでのみ例外仕様を使用するためにマクロを使用して、例外がドキュメントと一致することを確認するために明示的にコンパイルすることです。

8
Jeremy

「does n't throw」のように、唯一の有用な例外指定子は「throw()」です。

7
Harold Ekstrom

例外仕様は、C++で非常に便利なツールではありません。ただし、std :: unexpectedと組み合わせた場合は、/ is /が適切に使用されます。

いくつかのプロジェクトで私がしていることは、例外仕様を備えたコードであり、それからset_unexpected()を呼び出して、独自の設計の特別な例外をスローする関数を呼び出します。この例外は、構築時にバックトレースを取得し(プラットフォーム固有の方法で)、std :: bad_exceptionから派生します(必要に応じて伝播できるようにします)。通常のようにterminate()呼び出しが発生する場合、バックトレースはwhat()(およびそれを引き起こした元の例外。それを見つけるのは難しくありません)によって出力されます。予期しないライブラリ例外がスローされたなどの違反。

これを行うと、ライブラリ例外(std例外を除く)の伝播を許可せず、すべての例外をstd :: exceptionから派生させます。ライブラリがスローすることに決めた場合は、キャッチして独自の階層に変換し、常にコードを制御できるようにします。依存する関数を呼び出すテンプレート化された関数は、明白な理由で例外仕様を避ける必要があります。とにかく、ライブラリコードを含むテンプレート化された関数インターフェイスを持つことはまれです(そして、実際にテンプレートを便利な方法で使用するライブラリはほとんどありません)。

4
coppro

いいえ。それらを使用し、コードまたはコードによって呼び出されたコードのいずれかで指定しなかった例外がスローされた場合、デフォルトの動作はプログラムを即座に終了することです。

また、それらの使用は、C++ 0x標準の現在のドラフトでは非推奨になっていると思います。

4
Ferruccio

関数宣言を周囲のコメントよりもむしろ見たい人が使用するコードを書いている場合、仕様書はどの例外をキャッチしたいかを伝えます。

そうでない場合、throw()以外を使用して例外をスローしないことを示すことは特に有用ではありません。

3
Branan

"throw()"仕様により、コンパイラーは、関数が例外をスローしない(または、少なくとも例外をスローしないと約束する)ことがわかっている場合、コードフロー分析を行うときに最適化を実行できます。ラリーオスターマンはこのことについてここで簡単に語っています。

http://blogs.msdn.com/larryosterman/archive/2006/03/22/558390.aspx

3
TheJuice

通常、例外指定子は使用しません。ただし、問題の関数から他の例外が発生して、プログラムが正しいを決定できない場合、それは有用です。すべての場合において、その関数から予想される例外を明確に文書化してください。

はい、例外指定子を持つ関数からスローされる、指定されていない例外の予期される動作は、terminate()を呼び出すことです。

また、Scott Meyersがこのテーマをより効果的なC++で扱っていることにも注目します。彼のEffective C++とMore Effective C++は、強く推奨される本です。

2
Kris Kumler

はい、内部ドキュメントに興味がある場合。または、他の人が使用するライブラリを書いて、ドキュメントを参照せずに何が起こるかを伝えることができます。スローまたはスローしないことは、ほぼ戻り値のように、APIの一部と見なすことができます。

私は同意します、彼らはコンパイラの正確さJavaスタイルを強制するために本当に有用ではありませんが、それは何も無作法なコメントよりも優れています。

2
user10392

それらはユニットテストに役立ちます。テストを書くときに、関数が失敗したときに何をスローするかを知ることができますが、コンパイラーでそれらを取り巻く強制はありません。これらは、C++では必要のない余分なコードだと思います。確実に選択する必要があるのは、プロジェクトとチームメンバー全体で同じコーディング標準に従うことです。これにより、コードが読みやすくなります。

2
Odd

記事から:

http://www.boost.org/community/exception_safety.html

「例外セーフのジェネリックコンテナを記述することは不可能であることはよく知られています。」この主張は、Tom Cargill [4]がジェネリックスタックテンプレートの例外安全性の問題を調査した記事を参照してよく聞かれます。カーギルは彼の記事で多くの有用な質問を提起していますが、残念ながら彼の問題の解決策を提示することに失敗しています。1彼は解決策が不可能かもしれないと示唆して結論を​​下します。残念ながら、彼の記事は多くの人にその推測の「証拠」として読まれました。それが公開されて以来、C++標準ライブラリコンテナなど、例外に対して安全な汎用コンポーネントの多くの例がありました。

実際、テンプレートクラスの例外を安全にする方法を考えることができます。すべてのサブクラスを制御できない場合を除き、とにかく問題が発生する可能性があります。これを行うには、さまざまなテンプレートクラスによってスローされる例外を定義するtypedefをクラスに作成できます。これは、最初から設計するのとは対照的に、問題は後から常に取り組むことであると考えており、本当のハードルはこのオーバーヘッドだと思います。

0
Marius