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


123

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(...)として扱うため、標準への準拠は強くありません。

例外指定子を使用する必要があると思いますか?
「はい」または「いいえ」で答え、答えを正当化するいくつかの理由を提供してください。


7
「throw(...)」は標準のC ++ではありません。これは一部のコンパイラの拡張機能であり、通常、例外指定がないのと同じ意味です。
Richard Corden

回答:


97

番号。

いくつかの例を次に示します。

  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( ... )

    1つ目は拡張可能ではなく、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();
    }

    glib_f()スローすると終了します。これは(ほとんどの場合)本当に望んでいることではありません。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;
    }

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


1
3では、技術的にはstd :: unexpectedではなくstd :: terminateになります。ただし、この関数が呼び出されると、デフォルトでabort()が呼び出されます。これにより、コアダンプが生成されます。これは、処理されない例外よりどのように悪いのですか?(基本的に同じことを行います)
グレッグロジャース

6
@Greg Rogers:キャッチされない例外でもスタックの巻き戻しが行われます。つまり、デストラクタが呼び出されます。これらのデストラクタでは、リソースを正しく解放したり、ログを正しく書き込んだり、他のプロセスに現在のプロセスがクラッシュしたことを通知したりするなど、多くのことができます。要約すると、RAIIです。
paercebal 2008

省略:これはtry {...} catch (<specified exceptions>) { <do whatever> } catch (...) { unexpected(); ]、tryブロックが必要かどうかに関係なく、構成内のすべてを効果的にラップします。
David Thornley、2010年

4
@paercebal不正解です。キャッチされていない例外に対してデストラクタが実行されるかどうかは、実装によって定義されます。例外がキャッチされない場合、ほとんどの環境はスタック/実行デストラクタを巻き戻しません。例外がスローされて処理されない場合でもデストラクタが確実に実行されるようにするには(これは怪しい値です)、次のようなコードを記述する必要がありますtry { <<...code...>> } catch(...) /* stack guaranteed to be unwound here and dtors run */ { throw; /* pass it on to the runtime */ }
Logan Capaldo

アプリケーションを、スタックトレースを取得できる未処理の例外でクラッシュさせる方が、サイレント/暴力的に死ぬよりも常に優れていますterminate()。なぜただ電話をしないのabort()ですか?
curiousguy

42

C ++での例外指定は避けてください。あなたが質問で与える理由は、その理由のかなり良い始まりです。

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


3
@awoodland: 'dynamic-exception-specifications'(throw(optional-type-id-list))の使用はC ++ 11では非推奨です。それらはまだ標準にありますが、使用を慎重に検討する必要があるという警告が送信されたと思います。C ++ 11では、noexcept仕様と演算子が追加されています。私はnoexceptそれにコメントするのに十分な詳細を知りません。この記事はかなり詳細なようです:akrzemi1.wordpress.com/2011/06/10/using-noexcept そしてDietmarKühlが 2011年6月のオーバーロードジャーナルに記事を掲載しています:accu.org/var/uploads/journals/overload103.pdf
マイケルバー

@MichaelBurr Only throw(something)は役に立たないと考えられ、悪い考えです。throw()便利です。
curiousguy

" これは、多くの人々が例外仕様で行っていると考えていることです。bullet関数がリストされた例外(おそらくなし)のみをスローすることを保証します。bulletリストされた例外(おそらくなし)のみがスローされるという知識に基づいて、コンパイラの最適化を有効にします。上記の期待は、繰り返しますが、一見すると正しいと言えます」いいえ、上記の期待は完全に正しいです。
curiousguy

14

(C ++の)規則を除いて、標準
的に例外の指定子は、ほとんど失敗したC ++標準の実験だったと思います。
例外は、no throw指定子が役立つことですが、コードが指定子と一致することを確認するために、適切なtry catchブロックを内部で追加する必要もあります。Herb Sutterにはこの件に関するページがあります。ゴッチ82

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

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

例外保証

保障はありません:

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

基本的な保証:

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

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

これにより、メソッドが正常に完了することが保証され
ます。そうでない場合、例外がスローされ、オブジェクトの状態は変化しません。

投げない保証:

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


保証はすべてのC ++プログラマーが知っておくべきことですが、例外の仕様に関係しているようには思えません。
David Thornley、2010年

1
@David Thornley:例外の仕様は本来あるべきものであると保証します(つまり、強力なGを持つメソッドは、基本的なGを持つメソッドを保護なしで呼び出すことはできません)。残念ながら、それらがコンパイラとして有用な方法で強制されるのに十分に定義されているかどうかはわかりません。
マーティンヨーク

" これは、多くの人々が例外仕様で行っていると考えていることです。-関数がリストされた例外(おそらくなし)のみをスローすることを保証します。-リストされた例外(おそらくなし)のみがスローされるという知識に基づいて、コンパイラの最適化を有効にします。上記の期待は、もう一度、一見正しいことに一見似ています。 "実際、どちらも正確です。
curiousguy

@curiousguy。コンパイル時にチェックが実行されるため、Javaはこのようにしています。C ++はランタイムチェックです。だから、: Guarantee that functions will only throw listed exceptions (possibly none)。違います。関数がこれらの例外をスローした場合、アプリケーションが終了することを保証するだけです。Enable compiler optimizations based on the knowledge that only listed exceptions (possibly none) will be thrownそれはできません。先ほど指摘したように、例外がスローされないことは保証できません。
マーティンヨーク

1
@LokiAstari「コンパイル時にチェックが実行されるため、Javaはこのように動作します_」Java例外仕様は失敗した実験です。Javaには、最も有用な例外仕様throw()はありません(スローしません)。" 関数がこれらの例外をスローした場合にアプリケーションが終了することを保証するだけです "いいえ "真ではない"は真を意味します。C ++では、関数がを呼び出さないという保証はありませんterminate()。「先ほど指摘したとおり、例外がスローされないことは保証できません」関数がスローされないことを保証します。それはまさにあなたが必要とするものです。
curiousguy

8

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


7

唯一の有用な例外指定子は、「スローしない」の場合のように「スロー()」です。


2
役立つ理由を教えてください。
ブチオキサ

2
なぜそれが役に立たないのですか?一部の関数が左右中央に例外をスローし始めないことを知ることほど便利なものはありません。
マットジョイナー

1
詳細な説明については、Michael Burrの回答で参照されているHerb Sutterの議論を参照してください。
Harold Ekstrom

4

例外仕様は、C ++のすばらしいツールではありません。ただし、std :: unexpectedと組み合わせると、/ is /が有効に使用されます。

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

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


3

周囲のコメントよりも関数宣言を見る人が使用するコードを記述している場合、仕様は、キャッチしたい例外を通知します。

それ以外の場合、何かを使用することは特に便利だthrow()とは思いませんが、例外をスローしないことを示しています。


3

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

また、C ++ 0x標準の現在のドラフトでは、これらの使用は廃止されていると思います。


3

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

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


2

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

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

また、Scott Meyersが、より効果的なC ++でこの問題に取り組んでいることにも注目します。彼の効果的なC ++とより効果的なC ++は強く推奨される本です。


2

はい、内部のドキュメントに興味があるなら。または、他の人が使用するライブラリを作成して、ドキュメントを参照しなくても何が起きるかを知らせることができます。スローするかスローしないかは、戻り値とほとんど同じように、APIの一部と考えることができます。

私は同意します。これらはコンパイラでJavaスタイルの正確さを強制するのに実際には役立ちませんが、何もない、または無計画なコメントよりも優れています。


2

これらは単体テストに役立ちます。テストを作成するときに、関数が失敗したときに関数が何をスローするかを予測できますが、コンパイラーでそれらを強制することはありません。C ++では不要な余分なコードだと思います。コードを読みやすくするために、プロジェクトとチームメンバー全体で同じコーディング標準に従っていることを確認する必要があります。


0

記事から:

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

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

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


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.