例外の仕様が悪いのはなぜですか?


50

約10年以上前の学校に戻って、彼らは例外指定子の使用を教えていました。私のバックグラウンドは彼らの1人であるため、強制されない限り頑固にC ++を避けるトーバルディッシュCプログラマーなので、私は散発的にC ++になってしまい、私が教えられたので例外指定子を使用しています。

ただし、C ++プログラマの大半は、例外指定子に眉をひそめているようです。これらのようなさまざまなC ++の達人からの議論と議論を読みました。私の知る限り、それは次の3つに要約されます。

  1. 例外指定子は、残りの言語と矛盾する型システムを使用します(「シャドウ型システム」)。
  2. 例外指定子を持つ関数が、指定したもの以外の何かをスローする場合、プログラムは悪い、予期しない方法で終了します。
  3. 例外指定子は、今後のC ++標準で削除される予定です。

ここで何かが足りないのですか、それともすべての理由ですか?

私自身の意見:

1)に関して:それで何。C ++はおそらく、構文的にはこれまでに作成された中で最も一貫性のないプログラミング言語です。マクロ、goto / labels、undefined- / unspecified- / implementation-defined動作の大群(hoard?)、不十分に定義された整数型、すべての暗黙的な型昇格規則、friend、autoなどの特別な場合のキーワードがあります。 、登録、明示...など。おそらく、C / C ++のすべての奇妙さについての厚手の本をいくつか書くことができます。それでは、なぜ人々はこの特定の矛盾に反応するのでしょうか。これは、言語の他の多くのより危険な機能と比較して、小さな欠陥です。

2)に関して:それは私自身の責任ではありませんか?C ++で致命的なバグを作成できる方法は他にもたくさんありますが、なぜこの特定のケースはさらに悪いのでしょうか?throw(int)Crash_t を記述してからスローする代わりに、関数がintへのポインターを返し、ワイルドで明示的な型キャストを行い、Crash_tへのポインターを返すと主張することもできます。C / C ++の精神は、ほとんどの責任をプログラマに任せることでした。

では、利点はどうですか?最も明白なのは、関数が指定したもの以外の型を明示的にスローしようとすると、コンパイラーがエラーを出すことです。これに関しては標準が明確だと思います(?)。バグが発生するのは、関数が他の関数を呼び出し、その関数が間違った型をスローした場合のみです。

決定論的な組み込みCプログラムの世界から来た私は、関数が私に投げかけるものを正確に知ることを最も確実に好むでしょう。それをサポートする言語に何かがあれば、それを使用してみませんか?選択肢は次のようです:

void func() throw(Egg_t);

そして

void func(); // This function throws an Egg_t

呼び出し元が2番目のケースでtry-catchを実装することを無視/忘れる可能性は大きいと思いますが、1番目のケースではそうではありません。

私が理解しているように、これら2つの形式のいずれかが突然別の種類の例外をスローすることにした場合、プログラムはクラッシュします。最初のケースでは、別の例外をスローすることが許可されていないため、2番目のケースでは、SpanishInquisition_tをスローすることを誰も期待していないため、その式は本来あるべき場所にキャッチされません。

後者の場合、プログラムの最高レベルで最後の手段としてcatch(...)を使用することは、プログラムクラッシュよりも優れているようには見えません。「ねえ、あなたのプログラムのどこかで、奇妙な未処理の例外がスローされました。 。 "。例外がスローされた場所から遠く離れると、プログラムを回復できません。できることは、プログラムを終了することだけです。

また、ユーザーの観点からは、OSから「プログラムが終了しました。アドレス0x12345のBlablabla」という悪意のあるメッセージボックス、またはプログラムから「未処理の例外:myclass」という悪意のあるメッセージボックスを受け取ったとしても、それほど気にすることはありませんでした。 func.something」。バグはまだそこにあります。


今後のC ++標準では、例外指定子を放棄する以外の選択肢はありません。しかし、私はむしろ、「彼の法王がそれを述べているので、そうである」というよりも、なぜ彼らが悪いのかという堅実な議論を聞きたいと思います。おそらく、私がリストしたものよりも多くの議論がありますか、それとも私が理解している以上のものがありますか?


30
私はこれを「質問として偽装された暴言」として支持したいと思っています。E.specsについて3つの正当な点を尋ねますが、なぜC ++が面倒で、IのようなCの良いとりとめを気にしませんか?
マーティンBa

10
@Martin:私は偏見があり、すべての詳細が最新ではないことを指摘したいだけでなく、私は言語を素朴な目および/またはまだ損なわれていない目で見ていることを指摘したいからです。しかし、C C ++はすでに信じられないほど欠陥のある言語であるため、多少の欠陥はそれほど重要ではありません。投稿は、編集する前に実際にははるかに悪かったです:)例外指定子に対する議論も非常に主観的であるため、自分で主観的になることなく議論することは困難です。

SpanishInquisition_t!形〔人や映画などが〕とても面白い、人を笑わせる〔パーティーなどが〕大はしゃぎの、愉快に騒ぐ〔人が〕大喜びの、陽気な!私は個人的に、スタックフレームポインターを使用して例外をスローすることに感銘を受け、コードをよりクリーンにすることができるようです。ただし、例外を使用して実際にコードを記述したことはありません。昔ながらの私と呼んでも、戻り値は私にとってはうまく機能します。
シャーバズ

@Shahbaz行間を読むことができるように、私もかなり昔ながらですが、それでも、例外自体が良いか悪いかを実際に疑問に思ったことはありません。

2
過去形スロー投げていない、throwed。強い動詞です。
TRIG

回答:


48

例外仕様は弱く施行されており、したがって実際にはあまり達成されないため、また、UBを呼び出さずに終了()できるようにランタイムが予期しない例外をチェックするように強制するため、悪いです。 、これはかなりのパフォーマンスを浪費する可能性があります。

そのため、要約すると、例外仕様は言語で実際にコードをより安全にするほど強く強制されておらず、指定どおりに実装するとパフォーマンスが大幅に低下しました。


2
しかし、実行時チェックは例外仕様自体のせいではなく、誤った型がスローされた場合に終了する必要があると述べている標準のどの部分のせいでもありません。この動的チェックにつながる要件を単純に削除し、すべてをコンパイラーによって静的に評価できるようにした方が賢明ではないでしょうか?ここでRTTIが本当の犯人であるように聞こえます、または...?

7
@Lundin:C ++の関数からスローされる可能性があるすべての例外を静的に決定することはできませんなど)。Javaのような静的コンパイル時例外チェックを実装するには、言語の基本的な変更と多くのC互換性を無効にする必要があります。

3
@OrbWeaver:静的チェックはかなり可能だと思います-例外仕様は関数の仕様(戻り値など)の一部であり、関数ポインター、仮想オーバーライドなどには互換性のある仕様が必要です。主な異議は、それがオールオアナッシング機能だということです-静的な例外仕様を持つ関数は、レガシー関数を呼び出すことができませんでした。(C関数を呼び出すと、何もスローできないため問題ありません)。
マイクシーモア

2
@Lundin:例外仕様は削除されておらず、廃止されました。それらを使用するレガシーコードは引き続き有効です。
マイクシーモア

1
@Lundin:例外仕様を備えた古いバージョンのC ++は完全に互換性があります。以前にコードが有効だった場合、それらはコンパイルに失敗したり、予期せず実行したりすることはありません。
DeadMG

18

誰もそれらを使用しない理由の1つは、主な仮定が間違っているためです。

「最も明らかな[利点]は、関数が指定したもの以外の型を明示的にスローしようとすると、コンパイラーがエラーを出すことです。」

struct foo {};
struct bar {};

struct test
{
    void baz() throw(foo)
    {
        throw bar();
    }
};

int main()
{
    test x;
    try { x.baz(); } catch(bar &b) {}
}

このプログラムは、エラーや警告なしでコンパイルされます

さらに、例外が捕捉されたとしても、プログラムは終了します。


編集:あなたの質問のポイントに答えるために、キャッチ(...)(以上、キャッチ(STD :: exeption&)またはその他の基底クラス、およびその後、キャッチ(...)は)そうでない場合でも、まだ便利です何が間違っていたのかを正確に知る。

ユーザーが「保存」のメニューボタンを押したとします。メニューボタンのハンドラーは、保存するアプリケーションを招待します。これは無数の理由で失敗する可能性があります。ファイルが消えたネットワークリソース上にあり、読み取り専用ファイルがあり、上書き保存できませんでした。しかし、ハンドラーは何かが失敗した理由を本当に気にしません。成功したか失敗したかのどちらかだけを考慮します。それが成功した場合、すべてが良いです。失敗した場合、ユーザーに通知できます。例外の正確な性質は無関係です。さらに、適切で例外に対して安全なコードを記述すると、そのようなエラーは、エラーを気にしないコードであっても、ダウンさせることなくシステム全体に伝播できることを意味します。これにより、システムの拡張が容易になります。たとえば、データベース接続を介して保存するようになりました。


4
@Lundinそれは警告なしでコンパイルされました。いいえ、できません。信頼できません。関数ポインタ経由で呼び出すことも、仮想メンバー関数にすることもできます。派生クラスは派生クラスをスローします(基本クラスはおそらく知ることができません)。
カズドラゴン

大変な運。Embarcadero / Borlandは、「例外をスローして例外指定子に違反する」という警告を出します。どうやらGCCはそうではありません。ただし、警告を実装できなかった理由はありません。同様に、静的解析ツールはこのバグを簡単に見つけることができます。

14
@Lundin:間違った情報を主張したり繰り返したりしないでください。あなたの質問はすでに暴言のようなものであり、虚偽の主張は役に立たない。静的解析ツールは、これが発生したかどうかを検出できません。そのためには、停止問題を解決する必要があります。さらに、プログラム全体を分析する必要があり、非常に多くの場合、ソース全体を利用できません。
デビッド

1
同じ静的分析ツールの@Lundinは、どの例外がスタックから出るかを知ることができるため、この場合、例外仕様を使用しても、エラーケースを処理できるプログラムを停止させる可能性のある潜在的な偽陰性以外は何も購入しませんより良い方法(失敗を発表し、実行を続けるなど:例については、私の答えの後半を参照してください)。
カズドラゴン

1
@Lundinいい質問です。答えは、一般に、呼び出している関数が例外をスローするかどうかわからないということです。そのため、安全な仮定はすべてが例外をスローするということです。これらをキャッチする場所は、私がstackoverflow.com/questions/4025667/…で答えた質問です。特にイベントハンドラは、自然なモジュール境界を形成します。例外を一般的なウィンドウ/イベントシステム(たとえば)にエスケープできるようにすると、罰せられます。プログラムがそこで生き残るためには、ハンドラーがすべてをキャッチする必要があります。
カズドラゴン

17

Anders Hejlsbergとのこのインタビューは非常に有名です。その中で、彼は最初にC#設計チームがチェック例外を破棄した理由を説明します。簡単に言えば、2つの主な理由があります。バージョン管理性とスケーラビリティです。

OPはC ++に焦点を当てているのに対し、HejlsbergはC#について議論していますが、Hejlsbergが指摘する点はC ++にも完全に当てはまります。


5
「Hejlsbergが指摘する点はC ++にも完全に適用可能です」..間違っています!Javaで実装されたチェック済み例外は、呼び出し元にそれらを処理するように強制します(および/または呼び出し元の呼び出し元など)。例外スペック C ++では、(しばらくかなり弱いと役に立たない)のみシングル「関数呼び出し境界」に適用されないではないチェック例外が行うように「コールスタックを伝播します」。
マーティンBa

3
@Martin:C ++の例外仕様の振る舞いについては同意します。しかし、「ハイルスベルクが指摘する点はC ++にも完全に当てはまる」という言葉は、C ++仕様ではなく、ハイルスベルクが指摘する点を指します。つまり、ここでは、例外の伝播ではなく、プログラムのバージョン管理性とスケーラビリティについて説明しています。
CesarGon

3
Hejlsbergにはかなり早い時期に同意しませんでした。「チェック例外について心配しているのは、プログラマーに手錠をかけることです。チェックされた例外はそれらを助けません。例外処理を行う方法を教えてくれるこれらの独裁的なAPIデザイナーのようなものです。」---例外がチェックかされて、あなたはまだあなたが呼んでいるAPIによってスローされた例外を処理する必要があります。チェックされた例外は、単に明示的にします。
quant_dev

5
@quant_dev:いいえ、呼び出しているAPIによってスローされた例外を処理する必要はありません。完全に有効なオプションは、例外を処理せ、呼び出し元が処理できるように呼び出しスタックをバブルアップさせることです。
CesarGon

4
@CesarGon例外を処理しないことも、例外の処理方法を選択することです。
quant_dev
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.