同僚に例外/RAII の概念を説明したときの質問を思い出します。
とにかく、私はMartin Yorkの答えRAIIと例外に同意します
例外とデストラクタとの取引は何ですか?
多くの C++ 機能は、スローしないデストラクタに依存しています。
実際、RAII の全体的な概念とコード分岐 (リターン、スローなど) との連携は、割り当て解除が失敗しないという事実に基づいています。同様に、オブジェクトに高い例外保証を提供したい場合、一部の関数 (std::swap など) は失敗しないはずです。
デストラクタを介して例外をスローできないという意味ではありません。言語がこの動作をサポートしようとさえしないというだけです。
認可されたらどうなるの?
せっかくなので想像してみましたが・・・
デストラクタがリソースを解放できなかった場合、どうしますか? あなたのオブジェクトはおそらく半分破壊されています。その情報で「外部」キャッチから何をしますか? 再試行?(はいの場合、デストラクタ内から再試行してみませんか?...)
つまり、とにかく半分破壊されたオブジェクトにアクセスできたとしても、オブジェクトがスタック上にある場合はどうなるでしょうか (これが RAII の基本的な動作です)。スコープ外のオブジェクトにどのようにアクセスできますか?
例外内でリソースを送信しますか?
あなたの唯一の希望は、例外内のリソースの「ハンドル」を送信し、キャッチ内のコードを期待することです...もう一度割り当てを解除してみてください(上記を参照)?
さて、面白いことを想像してみてください:
void doSomething()
{
try
{
MyResource A, B, C, D, E ;
// do something with A, B, C, D and E
// Now we quit the scope...
// destruction of E, then D, then C, then B and then A
}
catch(const MyResourceException & e)
{
// Do something with the exception...
}
}
ここで、何らかの理由で D のデストラクタがリソースの割り当て解除に失敗したとします。キャッチによってキャッチされる例外を送信するようにコーディングしました。すべてがうまくいっている: 失敗を好きなように処理することができます (建設的な方法でどのようにするかはまだわかりませんが、今は問題ではありません)。
しかし...
複数の例外内で複数のリソースを送信していますか?
ここで、~D が失敗する可能性がある場合、~C も失敗する可能性があります。~B および ~A と同様に。
この単純な例では、「同時に」失敗した (スコープを終了する) 4 つのデストラクタがあります。必要なのは、1 つの例外をキャッチするのではなく、例外の配列をキャッチすることです (このために生成されたコードがスローしないことを願いましょう)。
catch(const std::vector<MyResourceException> & e)
{
// Do something with the vector of exceptions...
// Let's hope if was not caused by an out-of-memory problem
}
話を戻しましょう (私はこの音楽が好きです... ): スローされる例外はそれぞれ異なります (原因が異なるため: C++ では、例外は std::exception から派生する必要がないことに注意してください)。ここで、4 つの例外を同時に処理する必要があります。4 つの例外をタイプ別、およびスローされた順序で処理する catch 句をどのように記述できるでしょうか?
また、同じタイプの複数の例外があり、複数回の割り当て解除の失敗によってスローされた場合はどうなるでしょうか? そして、配列の例外配列のメモリを割り当てるときに、プログラムがメモリ不足になり、えっと... メモリ不足の例外をスローした場合はどうなるでしょうか?
割り当て解除が失敗した理由や別の方法で対応する方法を理解するのに時間を費やすのではなく、この種の問題に時間を費やしたいですか?
明らかに、C++ の設計者は実行可能な解決策を見つけられず、そこで損失を削減しました。
問題はRAII対例外ではありません...
いいえ、問題は、何もできないほど失敗することがあるということです。
RAII は、いくつかの条件が満たされている限り、例外とうまく機能します。その中で:デストラクタはスローしません。あなたが対立として見ているのは、例外とRAIIという 2 つの「名前」を組み合わせた 1 つのパターンの単なるコーナー ケースです。
デストラクタで問題が発生した場合は、敗北を受け入れ、回復できるものを回復する必要があります。「DB 接続の割り当てを解除できませんでした。申し訳ありません。少なくとも、このメモリ リークを回避して、このファイルを閉じましょう。」
例外パターンは C++ での主要なエラー処理 (と思われる) ですが、それだけではありません。他のエラー/ログ メカニズムを使用して、C++ 例外が解決策ではない例外的な (しゃれを意図した) ケースを処理する必要があります。
あなたは言語の壁に出会ったばかりなので、私が知っている、または聞いたことのない他の言語の壁は、家を壊すことなく正しく通り抜けました (C# の試みは価値のあるものでしたが、Java のものはまだ冗談であり、私を傷つけます) ... 私は、同じ問題で同じように黙って失敗するスクリプト言語については話しません)。
しかし、最終的には、どれだけ多くのコードを記述しても、ユーザーがコンピューターの電源を切ることによって保護されることはありません。
あなたができる最善を尽くして、あなたはすでにそれを書いています。私自身の好みは、投げるファイナライズ メソッド、手動でファイナライズされていないリソースをクリーニングするスローしないデストラクタ、およびデストラクタの失敗について警告するログ/メッセージ ボックス (可能であれば) です。
おそらく、あなたは正しい決闘をしていません。「RAII 対 例外」ではなく、「リソースを解放しようとしている 対 破壊の脅威にさらされても、絶対に解放したくないリソース」とすべきです。
:-)