C++では、例外処理は、例外が発生する前に作成されたローカルオブジェクトの自動破壊(デストラクタの呼び出し)を伴うスタックアンワインディングというメカニズムが導入されています。この現象は、リソースを正しく解放するために重要です。
C++を設計する際、ビルトインのガベージコレクタが不足しており、リソース(メモリ、ファイル、ソケット)の解放の責任はプログラマーにあります。例外処理は、エラーによるコードのロールバック時に正しくリソースを解放することを保証する必要がありました。
例外発生時にオブジェクトの自動破壊メカニズムがない場合、リソース漏れが発生します。各catchブロックでリソースを手動で解放しないと、コードは複雑になり、不安定になります。
C++はスタックアンワインディングを実装しており、例外がスローされると、throwの前にスタックにあったすべてのオブジェクトが作成の逆順で破壊されます。それらのデストラクタは自動的に呼び出されます。
リソースを常に解放する古典的な方法は、RAII(Resource Acquisition Is Initialization)パターンを使用することです:すべてのリソースはオブジェクトに「ラップ」され、それが破壊されると、そのリソースが解放されます。
コード例:
#include <iostream> #include <stdexcept> struct FileHandle { FILE* file; FileHandle(const char* path) { file = fopen(path, "r"); if (!file) throw std::runtime_error("Cannot open file"); } ~FileHandle() { if (file) fclose(file); } }; void processFile(const char* path) { FileHandle fh(path); // throwが発生した場合、ロールバック // ... ファイルの処理 throw std::runtime_error("Some error"); // fcloseは自動的に呼び出される }
主な特長:
なぜ、単にtry-catchを使ってcatchブロック内でdeleteやfcloseを手動で呼び出すことができないのですか?漏れを防ぐために。
答え:
それは不便で信頼性がありません:リソースを閉じるのを忘れることが簡単です、特に複数の出口点やネストされたリソースがある場合です。デストラクタは、例外が関数を「通過」しても呼び出されます。catchは必要ありません。
スタックアンワインディング時に、スタック上のオブジェクトに「ラップ」されていない場合、ヒープ内のオブジェクトは破壊されますか?
答え:
いいえ。スタックアンワインディングはスタック上に配置されたオブジェクトのみのデストラクタを呼び出します。ヒープオブジェクトを正しく破壊するためには、スタック内のオブジェクトがそれらを所有する必要があります(例えば、スマートポインタを通じて)。
スタックアンワインディング時にデストラクタが例外をスローした場合、どうなりますか?
答え:
スタックアンワインディングの過程で、いずれかのオブジェクトの破壊中に第2の例外がスローされると、プログラムはstd::terminate()を呼び出して終了します。デストラクタから例外をスローしてはいけません!
開発者がRAIIを使用せず、catchブロックを介してファイルを手動で閉じ、メモリを解放します。
利点:
欠点:
ファイルやリソースはRAIIラッパークラスにラップされます。結果:解放はデストラクタで発生し、throw/catchに依存しません。
利点:
欠点: