関数シグネチャでC++ throw
キーワードを使用することが悪い習慣と見なされる技術的な理由は何ですか?
bool some_func() throw(myExc)
{
...
if (problem_occurred)
{
throw myExc("problem occurred");
}
...
}
いいえ、良い習慣とはみなされません。それどころか、それは一般に悪い考えと見なされます。
http://www.gotw.ca/publications/mill22.htm 理由についてさらに詳しく説明しますが、問題の一部はコンパイラーがこれを強制できないことです。通常は望ましくない、実行時にチェックされます。また、いずれの場合も十分にサポートされていません。 (MSVCはthrow()を除く例外仕様を無視します。例外はスローされないことを保証するものとして解釈します。
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:空の可能性がある場合を除きますが、私があなただったら、それも避けたいです。
この質問に対する他のすべての答えにもう少し値を追加するには、質問に数分投資する必要があります。次のコードの出力は何ですか?
#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;
}
}
回答: here のように、プログラムはstd::terminate()
を呼び出すため、例外ハンドラーは一切呼び出されません。
詳細:最初のmy_unexpected()
関数が呼び出されますが、throw_exception()
関数プロトタイプに一致する例外タイプを再スローしないため、最終的にstd::terminate()
が呼び出されます。したがって、完全な出力は次のようになります。
user @ user:〜/ tmp $ g ++ -o except.test except.test.cpp
user @ user:〜/ tmp $ ./except.test
まあ-これは予想外でした
'int'のインスタンスをスローした後に呼び出される終了
中止(コアダンプ)
Throw指定子の唯一の実際的な効果は、myExc
とは異なるものが関数によってスローされると、(通常の未処理の例外メカニズムの代わりに)std::unexpected
が呼び出されることです。
関数がスローできる例外の種類を文書化するには、通常、次のようにします。
bool
some_func() /* throw (myExc) */ {
}
さて、このスロー仕様についてグーグルで調べながら、私はこの記事を見ました: ---(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()属性を使用してこれを確認すると、MethodThatCannotThrow()から例外をスローする方法がないことを知っているため、「bar」変数を完全に最適化できます。 throw()属性がないと、コンパイラは "bar"変数を作成する必要があります。MethodThatCannotThrowが例外をスローした場合、例外ハンドラはbar変数の値に依存する可能性があるためです。
さらに、prefastなどのソースコード分析ツールは、throw()アノテーションを使用してエラー検出機能を改善できます(たとえば、try/catchがあり、呼び出すすべての関数がthrow()としてマークされている場合、 try/catchは必要ありません(はい、後でスローする可能性のある関数を呼び出すと問題が発生します)。
スロー仕様が言語に追加されたとき、それは最善の意図を持っていましたが、実践はより実用的なアプローチを裏付けました。
C++の場合、私の一般的な経験則は、スロー指定のみを使用して、メソッドがスローできないことを示すことです。これは強力な保証です。それ以外の場合は、何でもスローできると想定します。
メンバー変数のみを返し、例外をスローできない可能性のあるインライン関数のノースロー指定は、一部のコンパイラーで使用されますpessimizations(a made -最適化の反対を表すWord)は、パフォーマンスに悪影響を与える可能性があります。これについては、Boostの文献で説明されています。 Exception-specification
一部のコンパイラでは、適切な最適化が行われ、その関数の使用がパフォーマンスに影響を与える場合、非インライン関数のノースロー指定が有益な場合がありますそれを正当化する方法。
私にとっては、それを使用するかどうかは、おそらくプロファイリングツールを使用して、パフォーマンスの最適化の一環として非常に重要な目で行われるように思えます。
急いでいる人のための上記のリンクからの引用(ナイーブコンパイラからのインライン関数でthrowを指定することの悪い意図しない効果の例を含む):
例外仕様の根拠
例外仕様[ISO 15.4]は、スローされる可能性のある例外を示すため、またはプログラマがパフォーマンスの向上を望んでいるためにコーディングされる場合があります。ただし、スマートポインターからの次のメンバーを検討してください。
T&operator *()const throw(){return * ptr; }
この関数は他の関数を呼び出しません。ポインタなどの基本的なデータ型のみを操作するため、例外仕様の実行時の動作を呼び出すことはできません。関数は完全にコンパイラーに公開されます。実際、インラインで宣言されているため、スマートコンパイラは、関数が例外をスローできないことを簡単に推測し、空の例外仕様に基づいて行ったのと同じ最適化を行うことができます。ただし、「ダム」コンパイラは、あらゆる種類の悲観化を行う場合があります。
たとえば、例外仕様がある場合、一部のコンパイラはインライン化をオフにします。一部のコンパイラは、try/catchブロックを追加します。このようなペシマイゼーションは、実際のアプリケーションでコードを使用できなくするパフォーマンス障害になる可能性があります。
最初は魅力的ですが、例外仕様は理解するために非常に慎重な思考を必要とする結果をもたらす傾向があります。例外仕様の最大の問題は、プログラマが実際に持っている効果ではなく、プログラマが望む効果を持っているかのようにそれらを使用することです。
非インライン関数は、「何もスローしない」例外仕様が一部のコンパイラで何らかの利点をもたらす可能性がある場所です。