関数のシグネチャにキーワードをスローする


199

throw関数シグネチャでC ++ キーワードを使用することが悪い習慣と見なされている技術的な理由は何ですか?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}

:この最近の関連する質問を参照してくださいstackoverflow.com/questions/1037575/...
laalto


1
noexcept変更何を?
Aaron McDaid、2015

12
プログラミング関連の何かについての意見を持つことは何の問題もありません。少なくともこの質問では、この終了基準は不適切です。それは興味深い答えを持つ興味深い質問です。
AndersLindén2016年

例外仕様はC ++ 11以降廃止されていることに注意してください。
フランソワアンドリュー

回答:


127

いいえ、それは良い習慣とは見なされていません。逆に、それは一般的に悪い考えと考えられています。

http://www.gotw.ca/publications/mill22.htmは理由についてより詳細に説明していますが、問題の一部はコンパイラーがこれを強制できないため、実行時にチェックする必要があり、通常は望ましくない。そして、それはどんな場合でもうまくサポートされていません。(MSVCは、例外がスローされないことを保証するものとして解釈するthrow()を除いて、例外指定を無視します。


25
はい。スロー(myEx)よりもコードに空白を追加する方法は他にもあります。
Assaf Lavie

4
ええ、例外仕様を発見したばかりの人は、コンパイラがそれらを強制できるJavaのように機能すると想定することがよくあります。C ++では、そのようなことは起こらないため、それらの有用性は大幅に低下します。
09年

7
しかし、文書化の目的はどうですか?また、試みてもキャッチできない例外が通知されます。
AndersLindén2016年

1
@AndersLindén文書化の目的は何ですか?コードの動作を文書化したいだけの場合は、その上にコメントを付けてください。2番目の部分の意味がわからない。あなたが試みても決してキャッチしない例外はありますか?
2016

4
コードの文書化に関しては、コードは強力だと思います。(コメントは嘘をつくことができます)。私が言及している「文書化」効果とは、どの例外をキャッチできるか、他の例外は確実にできないかが確実にわかるということです。
AndersLindén16年

57

Jalfはすでにそれにリンクしていますが、GOTWは例外仕様が期待するほど有用ではない理由を非常にうまく表現しています。

int Gunc() throw();    // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)

コメントは正しいですか?結構です。Gunc()確かに何かを投げるHunc()可能性があり、AまたはB以外のものを投げる可能性もあります!コンパイラは、もしそうなら無意味にそれらを打つことを保証します…おお、そしてほとんどの場合、あなたのプログラムも無意味に打つことを保証します。

それは結局のところです、おそらくあなたはたぶん電話をかけてterminate()しまい、あなたのプログラムは素早くしかし痛い死を遂げます。

GOTWの結論は次のとおりです。

コミュニティとして今日私たちが学んだ最高のアドバイスは次のとおりです。

  • 教訓その1:例外仕様を書かないこと。
  • 教訓その2:おそらく空のものを除いて、でも私があなただったら、それさえ避けよう。

1
なぜ例外をスローし、それを説明できないのかわかりません。別の関数によってスローされたとしても、どの例外がスローされる可能性があるかはわかっています。私が見ることができる唯一の理由は、それが退屈だからです。
MasterMastic 2013年

3
@ケン:要点は、例外仕様を作成すると、ほとんどの場合マイナスの結果になるということです。唯一のプラスの効果は、例外が発生する可能性があることをプログラマーに示すことですが、コンパイラーによって妥当な方法でチェックされないため、エラーが発生しやすく、したがって多くの価値はありません。
2013年

1
ああ、応答してくれてありがとう。それがドキュメントの目的だと思います。
MasterMastic 2013年

2
正しくありません。例外の仕様を記述する必要がありますが、その目的は、呼び出し元がキャッチしようとするエラーを伝えることです。
HelloWorld 2016

1
@StudentTが言うように:他の例外をさらにスローしないことを保証するのは関数の責任です。存在する場合、プログラムは正常に終了します。スローを宣言することは、この状況を処理することは私の責任ではないことを意味、呼び出し元はこれを行うのに十分な情報を持っている必要があります。例外を宣言しないことは、例外がどこでも発生する可能性があり、どこでも処理できることを意味します。それは確かに反OOPの混乱です。間違った場所で例外をキャッチするのは設計の失敗です。例外は例外であり、ほとんどの関数はとにかく空をスローする必要があるため、空をスローしないことをお勧めします。
JanTuroň2017

30

この質問に対する他のすべての回答にもう少し価値を追加するには、質問に数分を費やす必要があります。次のコードの出力は何ですか?

#include <iostream>
void throw_exception() throw(const char *)
{
    throw 10;
}
void my_unexpected(){
    std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
    std::set_unexpected(my_unexpected);
    try{
        throw_exception();
    }catch(int x){
        std::cout << "catch int: " << x << std::endl;
    }catch(...){
        std::cout << "catch ..." << std::endl;
    }
}

回答:ここで述べたように、プログラムが呼び出さstd::terminate()れるため、例外ハンドラーは呼び出されません。

詳細:最初の my_unexpected()関数が呼び出されますが、throw_exception()関数プロトタイプに対応する例外タイプが再度スローされないため、最終的にstd::terminate()は呼び出されます。したがって、完全な出力は次のようになります。

user @ user:〜/ tmp $ g ++ -o except.test except.test.cpp
user @ user:〜/ tmp $ ./except.test well-
これは
、 'int'
Aborted(core捨てられた)


12

スロー指定子の唯一の実用的な効果は、(通常の未処理の例外メカニズムの代わりに)myExc関数によってとは異なるものがスローされたstd::unexpected場合に呼び出されることです。

関数がスローできる例外の種類を文書化するために、通常は次のようにします。

bool
some_func() /* throw (myExc) */ {
}

5
std :: unexpected()を呼び出すと、通常はstd :: terminate()が呼び出され、プログラムが突然終了することにも注意してください。
STH

1
-そして、MSVCは少なくとも私の知る限りこの動作を実装していません。
09年

9

スロー仕様が言語に追加されたとき、それは最高の意図でありましたが、実践はより実用的なアプローチを生み出しました。

C ++では、私の一般的な経験則は、スロー仕様のみを使用して、メソッドがスローできないことを示すことです。これは強力な保証です。それ以外の場合は、何かがスローされる可能性があると想定します。


9

さて、このスロー仕様についてグーグルしながら、この記事を見てみました:-(http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx

上記のリンクが機能しているかどうかに関係なく、今後も使用できるように、ここでも一部を複製しています。

   class MyClass
   {
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow() throw()
    {
        return 100;
    };
    void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf("bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

"throw()"属性を使用してコンパイラーがこれを認識すると、コンパイラーは "bar"変数を完全に最適化できます。これは、MethodThatCannotThrow()から例外をスローする方法がないことがわかっているためです。throw()属性がない場合、MethodThatCannotThrowが例外をスローした場合、例外ハンドラーはbar変数の値に依存する場合があるため、コンパイラーは「bar」変数を作成する必要があります。

さらに、prefastのようなソースコード分析ツールは、throw()アノテーションを使用してエラー検出機能を向上させることができます(たとえば、try / catchがあり、呼び出すすべての関数がthrow()としてマークされている場合) try / catchは必要ありません(はい、後でスローする可能性のある関数を呼び出す場合は問題があります)。


3

メンバー変数を返すだけで例外をスローできない可能性があるインライン関数のno throw仕様は、パフォーマンスに悪影響を及ぼす可能性のある悲観化(最適化の反対の偽造語)を行うために一部のコンパイラーによって使用される場合があります。これはBoostの資料に記載されています例外仕様

いくつかのコンパイラ正しいの最適化が行われ、それはそれを正当化するという方法で、その機能のパフォーマンスに影響を使用している場合は非インライン関数にはスロー仕様が有益であろう。

私には、それを使用するかどうかは、おそらくプロファイリングツールを使用して、パフォーマンス最適化の取り組みの一環として非常に批判的な目で行われた呼び出しのように聞こえます。

急いでいる人のための上記のリンクからの引用(ナイーブコンパイラーからのインライン関数でスローを指定することの意図しない悪い影響の例が含まれています):

例外仕様の根拠

例外の仕様[ISO 15.4]は、スローされる可能性のある例外を示すために、またはプログラマーがパフォーマンスの向上を望んでいるために、コード化されることがあります。しかし、スマートポインターから次のメンバーを検討してください。

T&演算子*()const throw(){return * ptr; }

この関数は他の関数を呼び出しません。ポインタのような基本的なデータ型のみを操作するため、例外仕様の実行時の動作を呼び出すことはできません。関数は完全にコンパイラーに公開されます。実際、インラインで宣言されているため、スマートコンパイラは、関数が例外をスローできないことを簡単に推定し、空の例外仕様に基づいて行ったのと同じ最適化を行うことができます。ただし、「ダム」コンパイラは、あらゆる種類の悲観化を行う可能性があります。

たとえば、例外仕様がある場合、一部のコンパイラはインライン展開をオフにします。一部のコンパイラは、try / catchブロックを追加します。このような悲観化はパフォーマンスの障害になり、コードを実際のアプリケーションで使用できなくなります。

最初は魅力的ですが、例外の仕様は、理解するために非常に注意深い思考を必要とする結果をもたらす傾向があります。例外仕様の最大の問題は、プログラマーが実際の効果ではなく、プログラマーが望む効果を持っているかのようにそれらを使用することです。

非インライン関数は、「何もスローしない」例外仕様が一部のコンパイラでいくつかの利点を持つ可能性がある場所です。


1
「例外仕様の最大の問題は、プログラマーが実際に持っている効果ではなく、プログラマーが望む効果を持っているかのようにそれらを使用することです。」これが、マシンまたは人と通信するときのエラーの最大の理由です。つまり、私たちが言うことと私たちが言うことの違いです。
Thagomizer、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.