More C++ Idioms/リソース獲得は初期化である(Resource Acquisition Is Initialization)
出典: フリー教科書『ウィキブックス(Wikibooks)』
目次 |
[編集]
リソース獲得は初期化である(Resource Acquisition Is Initialization)
[編集] 意図
- スコープの最後でリソースが解放されることを保証する。
- 基本例外保証を提供する。
[編集] 別名
- オブジェクト生存期間前後での実行(Execute-Around Object)
- リソース解放は最終化である(Resource Release Is Finalization)
[編集] 動機
関数スコープで獲得されたリソースは、その所有権が別のスコープやオブジェクトに移動していない限り、そのスコープから離脱する前に解放されるべきである。 非常にしばしば、これは、一方はリソースを取得し他方はリソースを解放する関数を対で呼び出すことを意味する。例えば、new/delete、malloc/free、acquire/release、file-open/file-close、nested_count++/nested_count--(ネスト回数)などである。 リソース管理の「規約」のうち「解放(release)」する部分は非常に容易く書き忘れてしまう。 制御の流れが、例外や return によってスコープから離脱することで、リソース解放関数が決して呼び出されない場合がある。 現在および未来における全ての可能な場合に対して、プログラマがリソース解放操作を呼び出すと信頼することは非常に危険である。 以下にいくつかの例を示す。
void foo () { char * ch = new char [100]; if (...) if (...) return; else if (...) if (...) else throw "ERROR"; delete [] ch; // 呼び出されないかもしれない……メモリリーク(解放漏れ)! } void bar () { lock.acquire(); if (...) if (...) return; else throw "ERROR"; lock.release(); // 呼び出されないかもしれない……デッドロック! }
これは一般的には制御の流れの抽象化の問題である。 リソース解放は初期化である(Resource Acquisition is Initialization(RAII))イディオムは、C++ では非常に一般的なイディオムであり、賢い方法で「リソース解放」操作の呼び出しの責任を緩和する。
[編集] 解法とサンプルコード
発想はリソースの解放操作をそのスコープ中のオブジェクトのデストラクタ中にラップするというものである。 規格によって、return 文による場合でも例外による場合でも、制御のフローがスコープを離脱する際に、(成功裏に生成されたオブジェクトの)デストラクタが必ず呼び出されることが保証されている。
class AutoDelete { public: AutoDelete (T * p) : ptr_(p) {} ~AutoDelete () throw() { delete ptr_; } private: T *ptr_; }; class ScopedLock // スコープ内ロック(Scoped Lock)イディオム { public: ScopedLock (Lock & l) : lock_(l) { lock_.acquire(); } ~ScopedLock () throw () { lock_.release(); } private: Lock lock_; }; void foo () { X * p = new X; AutoDelete safe_del(p); // メモリはリーク(解放漏れ)しない if (...) if (...) return; // ここでは delete を呼ぶ必要はない。 // safe_del のデストラクタがメモリを delete する。 } void X::bar() { ScopedLock safe_lock(l); // ロックは確実に解放される if (...) if (...) throw "ERROR"; // ここで release を呼び出す必要はない。 // safe_lock のデストラクタがロックを解放する。 }
RAII イディオムでは、コンストラクタでのリソース獲得は必須ではないが、デストラクタでのリソース解放が鍵である。 そのため、(稀ではあるが)リソース解放は最終化である(Resource Release is Finalization)イディオムとしても知られている。 このイディオムでは、デストラクタが例外を送出しないことが重要である。 そのため、デストラクタは無送出例外指定(no-throw specification)を持つことがある(必須ではない)。 std::auto_ptr と boost::scoped_ptr によりメモリリソースに対して RAII を素早く使うことができる。 RAII は例外安全性を保証することにも使われる。 RAII は多数の try/catch ブロックの使用なしに、リソースリーク(解放漏れ)を避けることができ、ソフトウェア業界で広く使われている。
[編集] 帰結
RAII に制限がないわけではない。 メモリではなく、確定的に解放されなければならず、例外が送出されるかもしれないリソースは、C++ のデストラクタでは大抵うまく扱えない。 C++ のデストラクタでは、(どうやってもそこは終末であり)エラーを外側のスコープに伝えることができないからである。 戻り値はなく、例外を外部に伝播させてはならない。 例外の可能性があるならば、デストラクタはそれ自身の内部でどうにかして例外的な場合を処理しなければならない。 それでもなお、RAII は C++ で最も広く使われているリソース管理イディオムの地位にとどまっている。
[編集] 既知の利用
- 実質的に全ての自明ではない C++ ソフトウェア
- std::auto_ptr
- boost::scoped_ptr
- boost::mutex::scoped_lock
[編集] 関連するイディオム
- スコープ防壁(Scope Guard)
- 参照回数計測(Reference Counting)
- スコープ内ロック(Scoped Locking)イディオムは、RAII をミューテックス(mutex)やセマフォ(semaphore)のようなオペレーティングシステムの同期プリミティブに対して適用した特別な場合である。
[編集] References
- Wikipedia 上の Resource Acquisition Is Initialization on Wikipedia
- Wikipedia 日本語版上の RAII
- Exception Safety: Concepts and Techniques -- Bjarne Stroustrup
- The RAII Programming Idiom
- Sutter, Herb (1999). Exceptional C++. Addison-Wesley. ISBN 0-201-61562-2.
- C++ Patterns: Execute Around Sequences - Kevlin Henney