3

関数シグニチャでの使用(ではない)について多くの議論を読みましたが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
      [...]
    }
}

ここに、コンパイラがアセンブリレベルを生成しない疑似コードをいくつか含めました。ご覧のとおり、発生する可能性のある例外を知ることで、コードの量を減らすことができます。ここで破棄する追加の変数があれば、追加のコードはさらに長くなります。

4

3 に答える 3

5

関数のシグネチャの一部としてのコンパイラ検証済みの例外には、コンパイラの最適化とコンパイル時のエラーチェックという2つの(理論上の)利点があります。

Xコンパイラに関して、例外クラスをスローする関数とクラスの違いは何Yですか?最終的には...何もありません。Xコンパイラは、例外クラスでは実行できなかった、どのような最適化を実行できYますか?std::exception特別でない限り(そしてXそれから派生したものでYはないが)、コンパイラにとって何が重要でしょうか?

最終的に、コンパイラが最適化に関して気にするのは、関数が例外をスローするかどうかだけです。そのため、C ++ 11の標準化委員会は、関数が何もスローしないと述べているをthrow(...)支持しました。noexcept

コンパイル時のエラーチェックに関しては、Javaはこれがどれだけうまく機能するかを明確に示しています。あなたは関数を書いています、foo。あなたのデザインはそれが投げることを持っていますXそしてY。他のコードはを使用fooし、スローするものは何でもfooスローします。fooただし、例外仕様には「スローするものは何でも」とは記載されていません。具体的に記載する必要がありXますY

ここで、戻って変更fooし、スローされないようにしますXが、スローしZます。突然、プロジェクト全体のコンパイルが停止します。fooここで、例外仕様を一致するように変更するために、スローされたものをすべてスローするすべての関数に移動する必要がありますfoo

最終的に、プログラマーは手を挙げて、例外をスローすると言います。あなたがそのような機能を放棄するとき、それはその機能が良いよりも害を及ぼしているという事実上の承認です。

役に立たないというわけではありません。それらを実際に使用することは、それらが一般的に役に立たないことを示しているだけです。だから意味がありません。

さらに、C ++の仕様では、仕様がないということは、(Javaのように)何もスローされるのではなく、何かがスローされることを意味することを示していることを忘れないでください。言語を使用する最も簡単な方法は、まさにその方法です。チェックなしです。ですから、わざわざ使いたくないという人はたくさんいるでしょう。

多くの人が気にしたくない機能は何が良いのでしょうか。そうする人でさえ、一般的にそれから多くの悲しみを得るでしょう。

于 2012-07-05T23:52:51.203 に答える
1

つまり、関数本体には

スローA以外のスローはありません。Bを投げます。Cを投げる;;
throw(A、B、C)よりも制限の少ないthrow-signaturesを持つ関数/メソッド呼び出しはありません。

コードは、異なるマシンで異なる時間にコンパイルでき、ダイナミックライブラリを介して実行時にのみリンクできることを忘れないでください。コンパイラには、呼び出された関数のシグネチャのローカルバージョンが含まれている場合がありますが、実行時に実際に使用されているバージョンと一致しない場合があります。(例外が完全に一致しない場合、リンクを禁止するようにリンカーを変更できると思いますが、それは解決するよりも多くの煩わしさをもたらす可能性があります。)

于 2012-07-05T22:34:48.663 に答える
0

スロー仕様は、場合によっては、特に組み込みシステムで役立つ可能性があります。組み込みシステムでは、例外を完全に禁止することもできます。特に、スローが許可されている関数が明示的に指定されている関数のみである場合、スローできない関数を呼び出すときに例外処理のオーバーヘッドを排除することができます。例外処理に「メタデータ」を使用するシステムでも、例外ハンドラーがスタックフレームを理解できる必要があるという事実は、そうでなければ可能である可能性のある最適化を妨げることに注意してください。

スロー仕様が役立つ可能性がある別の状況は、コンパイラーがcatchステートメントに、「予期されていない」レイヤーを介してバブルアップしない例外のみをキャッチするように指定することを許可した場合です。C ++およびそれを借用する言語での例外処理の概念の主な弱点の1つは、呼び出されたルーチンで発生する例外を、それによって文書化された条件で、ネストされたサブルーチンで発生する例外と区別する良い方法がないことです。直接呼び出されたルーチンが予期していなかった理由。catch後者を捕まえることなく、ステートメントが前者に作用することができれば、それは役に立ちます。

于 2012-07-05T23:57:25.677 に答える