ProgrammingC++バックエンド開発者

C++における例外処理時のスタックアンワインディングとリソースの違いを説明してください。リソースを正しく解放することを保証するにはどうすればよいですか?

Hintsage AIアシスタントで面接を突破

答え。

問題の歴史:

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は例外が発生しても閉じられる

主な特徴:

  • スタックアンワインディングは、例外がスローされる際のオブジェクトの破棄に関する標準メカニズムです。
  • リソースの解放は常にデストラクタ内で行ってください。
  • RAIIまたは標準クラス(たとえば、スマートポインタ)を使用してください。

トリッキーな質問。

コード内に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)ではありません。

一般的なエラーとアンチパターン

  • try-catch内でリソースを手動でクリーンアップし、デストラクタを無視する
  • RAIIや標準クラスの代わりに生ポインタを使用する
  • 例外処理を抽象化し、リソースが解放されない(例:setjmp/longjmp)

実生活の例

ネガティブケース

プログラマーはnewでメモリを割り当て、例外を処理し、catchブロックでメモリを解放しますが、関数の他の終了方法を忘れます。

利点:

  • 初めはシンプルで「透過的」なように見える

欠点:

  • 予期しない場所で例外がスローされると、メモリリークが発生する
  • テストやメンテナンスが困難

ポジティブケース

すべてのリソースにstd::unique_ptrとRAIIクラスを使用し、解放がtry/catchに依存しないようにします。

利点:

  • リソースのリークがない
  • エラーハンドリングのロジックが簡素化される

欠点:

  • 標準ライブラリおよび言語のイディオムに対する理解が必要となる