関数シグニチャでの使用(ではない)について多くの議論を読みましたがthrow(X)
、ISO C ++で指定されている(そして現在のコンパイラで実装されている)方法はかなり役に立たないと思います。しかし、なぜコンパイラはコンパイル時に例外を強制することができないのでしょうか。
シグニチャに含まれる関数/メソッド定義を記述した場合throw(A,B,C)
、コンパイラは、指定された関数の実装が例外的に正しいかどうかを判断するのに多くの問題を抱えることはないはずです。つまり、関数本体には
throw
以外はありませんthrow A; throw B; throw C;
;throw (A,B,C)
;よりも制限の少ないthrow-signaturesを持つ関数/メソッド呼び出しはありません。
、少なくともtry{}catch()
他の投げられたタイプを捕まえることの外で。これらの要件が満たされていない場合にコンパイラがエラーを発生させる場合、すべての関数は「安全」である必要があり、のようなランタイム関数unexpected()
は必要ありません。これはすべて、コンパイル時に保証されます。
void fooA() throw (A){
}
void fooAB() throw (A,B){
}
void fooABC() throw (A,B,C){
}
void bar() throw (A){
throw A(); // ok
throw B(); // Compiler error
fooA(); // ok
fooAB(); // compiler error
fooABC(); // compiler error
try{
throw A(); // ok
throw B(); // ok
throw C(); // Compiler error
fooA(); // ok
fooAB(); // ok
fooABC(); // compiler error
} catch (B){}
}
これには、すべての非C ++レルムコードがthrow()
デフォルトで指定されている(デフォルトextern "C"
で想定されている必要があります)か、例外の相互運用性がある場合は、適切なヘッダー(少なくともC ++の場合)も指定する必要がありthrow
ます。そうしないと、異なるコンパイル単位で異なる関数/メソッドの戻り型を持つヘッダーを使用する場合と比較できます。警告やエラーは発生しませんが、明らかに間違っています。スローされた例外は署名の一部であるため、それらも一致する必要があります。
このような制約を適用すると、次の3つの効果があります。
- ランタイムチェックに必要な暗黙のブロックをすべて削除するため
try{}catch
、例外処理のパフォーマンスが向上します。 - 「例外がライブラリを大きくしすぎているので、それらをオフにします」という議論。追加のコードのほとんどは、すべての関数呼び出しでの不要な暗黙のthrow / catch命令にあるため、消えてしまいます。コードが適切に指定されていれば
throw
、そのほとんどはコンパイラーによって追加されません。 - 誰も例外を好まないようだったので、それはプログラミングの世界のほとんどを激怒させるでしょう。さて、これらは実際に使えるので、使い方を学ぶ必要があります。
古いコードに互換性コンパイラフラグを使用した場合、何も壊れませんが、新しい例外コードの方が高速であるため、それを使用して新しいコードを記述しないことには十分な動機があります。
私の質問を要約すると、なぜそのような強制はISO C ++で必要とされないのですか?そうでない強い理由はありますか?例外は別の関数の戻り値にすぎないといつも思っていましたが、自動的に制御されるため、次のような関数を記述しないようにすることができます。
std::pair<int, bool> str2int(std::string s);
int str2int(std::string s, bool* ok);
さらに、変数の追加の自動破棄とスタック上の複数の関数を介した伝播により、次のようなコードは必要ありません。
int doThis(){
int err=0;
[...]
if ((err = doThat())){
return err;
}
[...]
}
;そして、正しい型だけに関数を要求return
できるのなら、なぜそれを要求できないのですthrow
か?
例外指定子の方が優れているのはなぜですか?最初から説明したように作られていないのはなぜですか?
PS例外とテンプレートにいくつかの問題があるかもしれないことを私は知っています-この質問への答えに応じて、おそらく私はそれについて別の質問をします-今のところテンプレートを忘れましょう。
編集(@NicolBolasへの応答):
コンパイラは、Yでは実行できなかった例外クラスXでどのような最適化を実行できますか?
比較:
void fooA() throw (A){
}
void fooAB() throw (A,B){
}
void fooABC() throw (A,B,C){
}
void bar() throw (){
try{
fooA();
// if (exception == A) goto A_catch
fooAB();
// if (exception == A) goto A_catch
// if (exception == B) goto B_catch
fooABC();
// if (exception == A) goto A_catch
// if (exception == B) goto B_catch
// if (exception == C) goto C_catch
}
catch (A){ // :A_catch
[...]
}
catch (B){ // :B_catch
[...]
}
catch (C){ // :C_catch
[...]
}
}
と:
void fooA(){
}
void fooAB(){
}
void fooABC(){
}
void bar(){
try{
fooA();
// if (exception == A) goto A_catch;
// if (exception == B) goto B_catch;
// if (exception == C) goto C_catch;
// if (exception == other) return exception;
fooAB();
// if (exception == A) goto A_catch;
// if (exception == B) goto B_catch;
// if (exception == C) goto C_catch;
// if (exception == other) return exception;
fooABC();
// if (exception == A) goto A_catch;
// if (exception == B) goto B_catch;
// if (exception == C) goto C_catch;
// if (exception == other) return exception;
}
catch (A){ // :A_catch
[...]
}
catch (B){ // :B_catch
[...]
}
catch (C){ // :C_catch
[...]
}
}
ここに、コンパイラがアセンブリレベルを生成しない疑似コードをいくつか含めました。ご覧のとおり、発生する可能性のある例外を知ることで、コードの量を減らすことができます。ここで破棄する追加の変数があれば、追加のコードはさらに長くなります。