web-dev-qa-db-ja.com

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

10年以上前の学校に戻って、彼らはあなたに例外指定子を使うように教えていました。私の経歴は、強制されない限り頑固にC++を避けているTorvaldish Cプログラマーの1人であるため、たまにC++になるだけで、私は例外指定子を使用していますが、それは私が教えたことだからです。

ただし、大部分のC++プログラマーは、例外指定子を嫌うようです。私はさまざまなC++の教祖からの議論と引数を読みました (like like )。私が理解している限り、それは3つのことに要約されます。

  1. 例外指定子は、他の言語と矛盾する型システム(「シャドウ型システム」)を使用します。
  2. 例外指定子を含む関数が指定したもの以外をスローすると、プログラムは予期せぬ不正な方法で終了します。
  3. 例外指定子は、今後のC++標準で削除されます。

ここに何か不足していますか、それともこれらすべての理由ですか?

私自身の意見:

1)について:それで何ですか。 C++はおそらく、構文的にはこれまでに作成された中で最も一貫性のないプログラミング言語です。マクロ、goto/labels、undefined-/unspecified-/implementation-defined動作の大群(ハード?)、不十分に定義された整数型、すべての暗黙の型プロモーションルール、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を無視/無視する可能性が高いと思います。最初のケースではそうではありません。

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

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

また、ユーザーの視点から見ると、OSから「プログラムが終了しました。アドレス0x12345のBlablabla」という悪意のあるメッセージボックス、またはプログラムから「未処理の例外:myclass。 func.something」。バグはまだ残っています。


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

51
user29079

例外仕様は、強制が弱く、実際にはあまり実行されないために悪いです。また、UBを呼び出す代わりに、ランタイムに予期しない例外をチェックさせてterminate()を強制するため、例外仕様も悪いです。 、これはかなりの量のパフォーマンスを浪費する可能性があります。

つまり、要約すると、例外仕様は実際にコードをより安全にするほど強力に言語に強制されておらず、指定どおりに実装することはパフォーマンスの大きな浪費でした。

50
DeadMG

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

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

struct foo {};
struct bar {};

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

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

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

さらに、例外がキャッチされたとしても、プログラムは引き続き終了します。


編集:あなたの質問のポイントに答えるために、catch(...)(またはより良い、catch(std :: exeption&)または他の基本クラス、そしてそしてcatch(...))は、何がうまくいかなかったのか正確に知らなくても、まだ役に立ちます。

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

19
Kaz Dragon

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

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

17
CesarGon