質問

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

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

役に立ちましたか?

解決

いいえ。

その理由をいくつか例を挙げて説明します。

  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();
    }
    

    g 終了するとき lib_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;
    }
    

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

他のヒント

C++ では例外の指定を避けてください。質問に挙げた理由は、その理由を説明するための非常に良い出発点です。

ハーブ・サッターの作品を見る 「例外仕様についての実践的な考察」.

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

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

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

例外保証

保証なし:

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

基本保証:

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

強力な保証:(別名取引保証)

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

スロー保証なし:

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

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

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

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

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

例外仕様は、C++ ではそれほど便利なツールではありません。ただし、std::unexpected と組み合わせると、/is/ 良い使い方ができます。

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

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

周囲のコメントよりも関数宣言を参照したい人が使用するコードを作成している場合、仕様により、どの例外をキャッチする必要があるかがわかります。

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

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

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

通常、私は例外指定子を使用しません。ただし、問題の関数から他の例外が発生した場合、プログラムは決定的に実行できなくなります。 正しい, 、それなら役に立つかもしれません。いずれの場合も、その関数でどのような例外が予想されるかを明確に文書化してください。

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

また、Scott Meyers が「More Effects C++」でこのテーマに取り組んでいることにも注目します。彼の『Effective C++』と『More Effects C++』は強く推奨される書籍です。

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

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

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

記事より:

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

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

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

例外仕様 = ゴミ、30 歳以上の Java 開発者に聞いてください。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top