`catch(…){throw; } `悪い習慣ですか?


74

... 再スローせずにキャッチするのは本当に間違っていることに同意しますが、次のような構造を使用すると信じています。

try
{
  // Stuff
}
catch (...)
{
  // Some cleanup
  throw;
}

RAIIが適用されない場合受け入れられます。(質問しないでください...私の会社の全員がオブジェクト指向プログラミングを好むわけではなく、RAIIはしばしば「役に立たない学校のもの」と見られています...)

私の同僚は、どの例外がスローされるかを常に知っておく必要があり、次のような構造を常に使用できると言っています。

try
{
  // Stuff
}
catch (exception_type1&)
{
  // Some cleanup
  throw;
}
catch (exception_type2&)
{
  // Some cleanup
  throw;
}
catch (exception_type3&)
{
  // Some cleanup
  throw;
}

これらの状況に関して、十分に認められた良い慣行はありますか?


3
@Pubby:これがまったく同じ質問かどうかわかりません。リンクされた質問は「キャッチする必要があります」に関するものですが...、私の質問は「キャッチする必要があります...<specific exception>、再スローする前に」に焦点を当てています
11

53
それを言ってすみませんが、RAIIのないC ++はC ++ではありません。
fredoverflow

46
それで、あなたの牛労働者は、特定の問題に対処するために発明された技術を却下し、次に、劣った代替案のどれを使用すべきかについて口論するでしょうか?申し訳ありませんが、どのように見ても、それは愚かに思えます。
sbi

11
「再スローせずにキャッチすることは確かに間違っています」-あなたは間違っています。でmaincatch(...) { return EXIT_FAILURE; }うまくデバッガの下で実行されていないコードでは正しいかもしれません。あなたがつかまえなければ、スタックはほどけないかもしれません。デバッガーがキャッチしていない例外を検出した場合にのみ、それらに残すようにしmainます。
スティーブジェソップ

3
...そのため、「プログラミングエラー」であっても、そのことを知りたくないとは限りません。とにかく、あなたの同僚は優れたソフトウェアの専門家ではないので、sbiは、最初は慢性的に弱い状況に対処する最善の方法について話すことは非常に難しいと言います。
スティーブジェソップ

回答:


196

私の同僚は、どの例外がスローされるかを常に知っておくべきだと言っています[...]

あなたの同僚は、私がそれを言うのは嫌いだと思いますが、明らかに汎用ライブラリに取り組んだことがありません。

例外の安全性を保証しながら、コピーコンストラクターが何をスローするかを知っているふりをするようなクラスでstd::vectorも、一体どのようにできますか?

呼び出し時に呼び出される側がコンパイル時に何をするかを常に知っていれば、ポリモーフィズムは役に立ちません!時々、全体の目標は、下位レベルで発生することを抽象化することであるため、具体的に何が起こっているのか知りたくない場合があります!


32
実際、たとえ例外がスローされることを知ったとしても。このコード複製の目的は何ですか?処理が異なる場合を除き、例外を列挙して知識を誇示することは意味がありません。
マイケルクレリン-ハッカー

3
@ MichaelKrelin-hacker:それも。また、コード内のすべての可能な例外をリストすることは後でバグを引き起こす傾向があったため、例外仕様を非推奨にしているという事実に加えてください...それは史上最悪のアイデアです。
Mehrdad

4
そして、私を悩ますのは、「役に立たない学校のもの」として有用で便利なテクニックを見ることに結びつけられるとき、そのような態度の起源であることができることです。しかし、よく...
マイケルKrelin -ハッカー

1
+1、すべての可能なオプションの列挙は、将来の失敗に対する優れたレシピ...です。一体なぜ誰かがそれをすることを選んだのでしょうか?
littleadv

2
素敵な答え。サポートしなければならないコンパイラがエリアXにバグがある場合、エリアXの機能を使用することは、少なくとも直接使用しないことは賢明ではないと言及することで、メリットが得られる可能性があります。たとえば、会社に関する情報を考えると、Visual C ++ 6.0を使用していても驚かないでしょう(Visual C ++ 6.0では、2回呼び出される例外オブジェクトデストラクターなど)。この日が、マニフェストするために慎重な手配が必要です。
アルフP.シュタインバッハ

44

あなたが巻き込まれているように見えるのは、ケーキを持って食べようとする誰かの特定の地獄です。

RAIIと例外は、連動するように設計されています。RAIIは、あなたがしない手段である必要があり、多くの書き込みにcatch(...)クリーンアップを実行するステートメントを。もちろん、それは自動的に行われます。また、例外はRAIIオブジェクトを操作する唯一の方法です。コンストラクターは成功またはスロー(またはオブジェクトをエラー状態にするが、だれがそれを望むのか)しかできないためです。

catch文は、2つのいずれかの操作を行うことができますエラーまたは例外的な状況を扱う、またはクリーンアップ作業を行います。両方catchを実行することもありますが、これらの少なくとも1つを実行するためのすべてのステートメントが存在します。

catch(...)適切な例外処理を行うことができません。例外が何なのかわかりません。例外に関する情報を取得できません。特定のコードブロック内の何かによって例外がスローされたという事実以外の情報はまったくありません。このようなブロックでできる唯一の正当なことは、クリーンアップを行うことです。そして、それはクリーンアップの終わりに例外を再スローすることを意味します。

例外処理に関してRAIIが提供するのは、無料のクリーンアップです。すべてが適切にRAIIカプセル化されている場合、すべてが適切にクリーンアップされます。catchステートメントをクリーンアップする必要はもうありません。その場合、catch(...)ステートメントを記述する理由はありません。

だから私はそれcatch(...)がほとんど悪であることに同意するだろう... 暫定的に

その規定はRAIIの適切な使用です。それなしでは、特定のクリーンアップを実行できる必要があります。それを回避することはありません。クリーンアップ作業を行える必要があります。例外をスローすると、コードが適切な状態になるようにする必要があります。そして、catch(...)ある重要なそうすることでツール。

片方をもう片方なしに持つことはできません。RAII も悪い catch(...)も言えません。これらのうち少なくとも1つが必要です。そうでなければ、あなたは例外的に安全ではありません。

もちろん、catch(...)RAIIでさえも消去できない有効な、しかしまれな使い方が1つありexception_ptrます。それは、誰かに転送することです。通常、promise/futureまたは同様のインターフェースを介して。

私の同僚は、どの例外がスローされるかを常に知っておく必要があり、次のような構造を常に使用できると言っています。

あなたの同僚はバカです(またはひどく無知です)。これは、彼があなたに書くことを提案しているコピーアンドペーストコードの量のためにすぐに明らかになるはずです。これらの各catchステートメントのクリーンアップはまったく同じになります。可読性は言うまでもなく、メンテナンスの悪夢です。

つまり、これはRAII が解決するために作成された問題です(他の問題を解決しないわけではありません)。

この概念について私を混乱させるのは、ほとんどの人がRAIIが悪いと主張する方法に一般的に逆行しているということです。一般に、引数は「RAIIは、コンストラクターの失敗を通知するために例外を使用する必要があるため悪い。しかし、安全ではなく、catchすべてをクリーンアップするために多くのステートメントが必要になるため、例外をスローできない」。RAII はRAIIの欠如が引き起こす問題を解決するため、これは壊れた議論です。

おそらく、彼はRAIIに反対しています。なぜなら、それは詳細を隠すからです。デストラクタの呼び出しは、自動変数ではすぐには表示されません。したがって、暗黙的に呼び出されるコードを取得します。一部のプログラマーは本当にそれを嫌います。どうやら、彼らが3つのcatchステートメントを持っていると考えるポイントまでは、コピーアンドペーストコードですべて同じことを行うのが良い考えです。


2
強力な例外安全性保証を提供するコードを書かないようです。RAIIは、基本的な保証の提供を支援します。ただし、強力な保証を提供するには、システムを関数が呼び出される前の状態に戻すために、いくつかのアクションを元に戻す必要があります。基本的な保証はクリーンアップであり、強力な保証はロールバックです。ロールバックの実行は機能固有です。したがって、「RAII」に入れることはできません。そして、それがキャッチオールブロックが便利になるときです。強力な保証付きでコードを記述する場合は、キャッチオールを使用します。
anton_rh

@anton_rh:おそらく、しかしその場合でも、キャッチオールステートメントは最後の手段です。推奨されるツールは、例外で元に戻す必要のある状態を変更する前にスローするすべてを実行することです。明らかに、すべての場合にそのようにすべてを実装することはできませんが、それは強力な例外保証を得るための理想的な方法です。
ニコルボーラス

14

本当に2つのコメント。1つ目は、理想的な世界では、どのような例外がスローされる可能性があるかを常に知っておく必要があるということです。しかし、もっと重要なことです。可能性のある例外のすべてを正確に知っていても、それはここで関連していますか? catch (...)よりはるかに良い意図を表現catch ( std::exception const& )しても、すべての可能な例外から派生したとすると、 std::exception(理想的な世界の場合)。いくつかのcatchブロックの使用に関して、すべての例外に共通のベースがない場合:それは完全に難読化であり、メンテナンスの悪夢です。すべての動作が同一であることをどのように認識しますか?そして、それが意図でしたか?また、動作を変更する必要がある場合(バグ修正など)はどうなりますか?見逃すのはとても簡単です。


3
実際、同僚は自分の例外クラスを設計しました。この例外クラスは、コードベースから派生したものではなくstd::exception、その使用を毎日実行しようとしています。私の推測では、彼は自分で書いていないコードや外部ライブラリを使用したことで私を罰しようとしています。
ere

17
@ereOn同僚がトレーニングを急ぐ必要があるように思えます。いずれにせよ、私はおそらく彼によって書かれたライブラリーの使用を避けるでしょう。

2
テンプレートと、どの例外がスローされるかを知ることは、ピーナッツバターと死んだヤモリのように一緒になります。std::vector<>なんらかの理由であらゆる種類の例外をスローできるような単純なもの。
デビッドソーンリー

3
コールツリーのさらに下のバグ修正で明日までにどのような例外がスローされるかを正確にどのように知っていますか?
マッテンツ

11

あなたの同僚はいくつかの良いアドバイスを混同していると思います-あなたはcatchそれらを再スローしない場合にのみ、ブロック内の既知の例外を処理すべきです。

これの意味は:

try
{
  // Stuff
}
catch (...)
{
  // General stuff
}

それは静かに非表示になりますので、不良である任意のエラーを。

しかしながら:

try
{
  // Stuff
}
catch (exception_type_we_can_handle&)
{
  // Deal with the known exception
}

大丈夫-何を扱っているかはわかっているので、呼び出し元のコードに公開する必要はありません。

同様に:

try
{
  // Stuff
}
catch (...)
{
  // Rollback transactions, log errors, etc
  throw;
}

一般的なエラーを処理するコードは、それらを引き起こすコードと一緒にすべきです。トランザクションのロールバックなどが必要であることを、呼び出し先に依存するよりも優れています。


9

いずれかの答えはいまたはいいえがなぜそうなのかの根拠を伴うべきです。

私がそのように教えられたという理由だけで間違っていると言うことは、盲目的な狂信です。

//Some cleanup; throwあなたの例のように同じことを数回書くのは間違っています。なぜならコードの重複であり、メンテナンスの負担になるからです。一度だけ書いたほうがいいです。

catch(...)を処理する方法を知っている例外のみを処理する必要があるため、すべての例外を黙らせるためにa を書くことは間違っています。

ただし、を再スローするcatch(...)と、例外を実際に処理していないため、後者の理論的根拠は適用されなくなります。したがって、これを推奨しない理由はありません。

実際、何の問題もなく機密機能にログインするためにこれを行ってきました。

void DoSomethingImportant()
{
    try
    {
        Log("Going to do something important");
        DoIt();
    }
    catch (std::exception &e)
    {
        Log("Error doing something important: %s", e.what());
        throw;
    }
    catch (...)
    {
        Log("Unexpected error doing something important");
        throw;
    }
    Log("Success doing something important");
}

2
Log(...)投げられないことを願いましょう。
デデュプリケーター

2

私はここの投稿の雰囲気に概ね同意します。特定の例外をキャッチするパターンは本当に嫌いです。この構文はまだ初期段階にあり、冗長なコードにまだ対処できないと思います。

しかし、誰もがそれを言っているので、私はそれらを控えめに使用しているにもかかわらず、私はしばしば「catch(Exception e)」ステートメントの1つを見て、「くそー、私は電話したいというのも、後でアクセスするときに、意図が何であり、クライアントが一目で何をスローする可能性があるかを知るのはいいことだからです。

私は「常にxを使用する」という態度を正当化するわけではありません。それらをリストするのを見るのは実にいいことだと言っているだけで、だからこそ「正しい」方法だと思う人もいるでしょう。

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