RAII
リソース取得は初期化である (Resource Acquisition Is Initialization) (RAII) は、使用する前に取得しなければならないリソース (確保したヒープメモリ、開いたファイル、ロックしたミューテックス、ディスク空間、データベース接続など、供給が制限されているあらゆるもの) のライフサイクルをオブジェクトの生存期間に束縛するための、 C++ のプログラミングテクニック[1][2]です。
RAII は、そのオブジェクトにアクセスできるあらゆる関数がそのリソースを利用可能であることを保証します (リソースの利用可能性はクラスの不変条件であり、冗長な実行時の判定を除去します)。 また、対応する制御オブジェクトの生存期間が終了したときにすべてのリソースが取得の逆順で解放されることも保証します。 同様に、リソースの取得が失敗した (コンストラクタが例外を投げた) 場合は、完全に構築されたすべてのメンバおよび基底部分オブジェクトによって取得されたすべてのリソースが初期化の逆順で解放されます。 これはリソースリークを取り除き例外安全を保証するためにコア言語の機能 (オブジェクトの生存期間、スコープの終了、初期化の順序およびスタックの巻き戻し) を活用します。 このテクニックは、 RAII オブジェクトの生存期間がスコープを終了することによって終了する場合の基本的なユースケースにちなんで、スコープに縛られたリソース管理 (Scope-Bound Resource Management) (SBRM) とも言います。
RAII は以下のように要約できます。
- 各リソースを以下のようなクラスにカプセル化します。
- コンストラクタは、リソースを取得し、クラスのすべての不変条件を確立します。 または、それができない場合は例外を投げます。
- デストラクタは、リソースを解放します。 例外を投げることはありません。
- 必ず以下のいずれかのような RAII クラスのインスタンスを通してリソースを使用します。
- 自動記憶域期間またはそれ自身の一時的な生存期間を持つ。 または、
- 自動または一時オブジェクトの生存期間に縛られた生存期間を持つ。
ムーブセマンティクスは、リソースの安全性を維持しつつ、オブジェクト間で、スコープを超えて、およびスレッドの内外へ、リソースの所有権を安全に転送することを可能とします。
open()/close() や lock()/unlock()、 init()/copyFrom()/destroy() などのメンバ関数持つオブジェクトは、 RAII でないクラスの典型的な例です。
std::mutex m; void bad(){ m.lock();// ミューテックスを取得します。 f();// f() が例外を投げると、ミューテックスは解放されません。if(!everything_ok())return;// 早期リターン。 ミューテックスは解放されません。 m.unlock();// bad() がこの文に達すると、ミューテックスは解放されます。} void good(){std::lock_guard<std::mutex> lk(m);// RAII クラス。 ミューテックスの取得は初期化です。 f();// f() が例外を投げると、ミューテックスは解放されます。if(!everything_ok())return;// 早期リターン。 ミューテックスは解放されます。}// good() が普通に終了すると、ミューテックスは解放されます。
[編集]標準ライブラリ
RAII に従って自身のリソースを管理する C++ 標準ライブラリのクラス (std::string、 std::vector、 std::thread およびその他多数) は、コンストラクタでリソースを取得し (エラーの場合は例外を投げ)、デストラクタでそれを解放し (例外を投げることはありません)、明示的なクリーンアップを要求しません。
さらに、標準ライブラリはユーザ提供リソースを管理するための RAII ラッパーをいくつか提供しています。
- std::unique_ptr と std::shared_ptr。 動的確保されたメモリ、または (ユーザ提供のデリータを使用して) プレーンなポインタによって表現されたあらゆるリソースを管理します。
- std::lock_guard、 std::unique_lock、 std::shared_lock。 ミューテックスを管理します。
[編集]ノート
RAII は、使用する前に取得しないリソース (CPU 時間、コア、キャッシュ容量、エントロピープールの容量、ネットワーク帯域、電力消費、スタックメモリなど) の管理には、適用されません。