throw
関数シグネチャでC ++ キーワードを使用することが悪い習慣と見なされている技術的な理由は何ですか?
bool some_func() throw(myExc)
{
...
if (problem_occurred)
{
throw myExc("problem occurred");
}
...
}
noexcept
変更何を?
throw
関数シグネチャでC ++ キーワードを使用することが悪い習慣と見なされている技術的な理由は何ですか?
bool some_func() throw(myExc)
{
...
if (problem_occurred)
{
throw myExc("problem occurred");
}
...
}
noexcept
変更何を?
回答:
いいえ、それは良い習慣とは見なされていません。逆に、それは一般的に悪い考えと考えられています。
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;
}
}
回答:ここで述べたように、プログラムが呼び出さ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捨てられた)
さて、このスロー仕様についてグーグルしながら、この記事を見てみました:-(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は必要ありません(はい、後でスローする可能性のある関数を呼び出す場合は問題があります)。
メンバー変数を返すだけで例外をスローできない可能性があるインライン関数のno throw仕様は、パフォーマンスに悪影響を及ぼす可能性のある悲観化(最適化の反対の偽造語)を行うために一部のコンパイラーによって使用される場合があります。これはBoostの資料に記載されています:例外仕様
いくつかのコンパイラ正しいの最適化が行われ、それはそれを正当化するという方法で、その機能のパフォーマンスに影響を使用している場合は非インライン関数にはスロー仕様が有益であろう。
私には、それを使用するかどうかは、おそらくプロファイリングツールを使用して、パフォーマンス最適化の取り組みの一環として非常に批判的な目で行われた呼び出しのように聞こえます。
急いでいる人のための上記のリンクからの引用(ナイーブコンパイラーからのインライン関数でスローを指定することの意図しない悪い影響の例が含まれています):
例外仕様の根拠
例外の仕様[ISO 15.4]は、スローされる可能性のある例外を示すために、またはプログラマーがパフォーマンスの向上を望んでいるために、コード化されることがあります。しかし、スマートポインターから次のメンバーを検討してください。
T&演算子*()const throw(){return * ptr; }
この関数は他の関数を呼び出しません。ポインタのような基本的なデータ型のみを操作するため、例外仕様の実行時の動作を呼び出すことはできません。関数は完全にコンパイラーに公開されます。実際、インラインで宣言されているため、スマートコンパイラは、関数が例外をスローできないことを簡単に推定し、空の例外仕様に基づいて行ったのと同じ最適化を行うことができます。ただし、「ダム」コンパイラは、あらゆる種類の悲観化を行う可能性があります。
たとえば、例外仕様がある場合、一部のコンパイラはインライン展開をオフにします。一部のコンパイラは、try / catchブロックを追加します。このような悲観化はパフォーマンスの障害になり、コードを実際のアプリケーションで使用できなくなります。
最初は魅力的ですが、例外の仕様は、理解するために非常に注意深い思考を必要とする結果をもたらす傾向があります。例外仕様の最大の問題は、プログラマーが実際の効果ではなく、プログラマーが望む効果を持っているかのようにそれらを使用することです。
非インライン関数は、「何もスローしない」例外仕様が一部のコンパイラでいくつかの利点を持つ可能性がある場所です。