問題の歴史:
C++は最初からパフォーマンスに重点を置いて設計されたため、リソース(メモリ、ファイル、スレッド、ソケット)の管理は手動で行われることが多いです。例外が発生した場合、捕獲されたリソースをクリーンアップする必要があります。スタックアンワインディング(stack unwinding)とは、例外がスローされる際にC++が関数の正常な終了を行うために使用するメカニズムです。
問題:
例外がスローされると、制御はすぐにcatchブロックに移りますが、中間関数は「アンワインド」されます:そのデストラクタは呼び出されますが、リソース解放関数の明示的な呼び出しは省略されることがあります(たとえば、リソースを自動的に解放するオブジェクトが使用されていない場合など)。
解決策:
C++では、リソースの解放をデストラクタに委任し、手動で解放関数を呼び出さないことが推奨されます。RAII(Resource Acquisition Is Initialization)パターンは、リソースの解放を自動化するための明確な方法です。スタックアンワインディング時にデストラクタが呼び出され、関数からの出力方法に関係なくリソースが解放されます。
コード例:
#include <fstream> #include <stdexcept> void readFile(const std::string& filename) { std::ifstream file(filename); // 例外が発生しても正しく開かれ、閉じられる if (!file.is_open()) { throw std::runtime_error("ファイルを開けません"); } // ...ファイルを読む } // fileは例外が発生しても閉じられる
主な特徴:
コード内にdelete ptr;がcatchブロックにある場合、それだけでメモリを解放できますか?
いいえ、メモリの割り当てとcatchブロックの間に例外が発生すると、メモリが解放されない可能性があります。std::unique_ptrを使用するか、デストラクタ内でdeleteを書く方が良いでしょう。
コード例:
void foo() { int* data = new int[10]; // ... throw std::runtime_error("失敗"); delete[] data; // 例外時には呼び出されない }
スタックアンワインディングは、スタック上に配置されたオブジェクトのデストラクタの呼び出しをスキップする可能性がありますか?
いいえ、(例外がスローされる前に破棄されていない)すべてのローカルオブジェクトは、作成順に逆順で破棄され、デストラクタは確実に呼び出されます。
tryブロックからgotoやlongjmpを使用して抜けることができ、デストラクタの呼び出しを期待できますか?
いいえ。C++は、例外によるスタックアンワインディング時にのみデストラクタを呼び出すことを保証しており、正しくない制御の流れ(gotoやsetjmp/longjmp)ではありません。
プログラマーはnewでメモリを割り当て、例外を処理し、catchブロックでメモリを解放しますが、関数の他の終了方法を忘れます。
利点:
欠点:
すべてのリソースにstd::unique_ptrとRAIIクラスを使用し、解放がtry/catchに依存しないようにします。
利点:
欠点: