RAII
[Wikipedia|▼Menu]

RAII(Resource Acquisition Is Initialization)は、日本語では「リソース取得は初期化である」「リソースの確保は初期化時に」「リソースの取得と初期化」などの意味を持ち、資源(リソース)の確保と解放を、クラス型の変数の初期化と破棄処理に結び付けるというプログラミングのテクニックである。特にC++D言語で一般的であり、デストラクタをサポートしないC言語などに対する優位性や利便性のうちのひとつとなっている。

RAIIでは、資源の取得をクラス型変数の構築(初期化)時に、また返却を破壊時に行う。特にプログラムの制御フローが自動変数の属するブロックを抜けるとき、その変数のデストラクタが自動的に呼ばれるため、デストラクタを適切に記述したクラス型変数の寿命が終わるとすぐに資源が返却されることが保証できるようになる。これは例外が発生したときでも同様であるため、RAIIは例外安全なコードを書くための鍵となる概念となった (Sutter 1999)。
典型的な用法

RAIIの最も基本的な活用例は、動的確保されたメモリを自動解放するスマートポインタ (smart pointer) である。C++においてnew演算子(またはnew[]演算子)で動的に確保されたメモリは、不要になったときにその領域を指すポインタを経由してdelete演算子(またはdelete[]演算子)で明示的に解放しなければならない。もし解放忘れがあるとメモリリークにつながるが、解放忘れがないように細心の注意を払って、コードパスに手動でひとつひとつ削除処理を記述していくことは非常に手間がかかる。一方、C++ではオブジェクトをスタックに割り当てることも可能であり、動的確保されたメモリを指すポインタをラップするスマートポインタクラスのオブジェクトをスタックに割り当て、ラッパーオブジェクトの寿命が尽きた時点で自動的に呼び出されるデストラクタを利用することにより、動的確保されたメモリの解放を明示的に逐一記述することなく、暗黙的かつ確実に実行させることができる。標準C++ライブラリにおける動的配列クラステンプレートのstd::vectorなども、プログラマが明示的にnew[]およびdelete[]を呼び出す必要のないRAIIクラスの一種である。

RAIIはファイル操作にも用いられる。C言語ではファイルアクセスの際、fopen()関数により取得したFILEオブジェクトを明示的にfclose()関数で解放することでファイルを閉じる必要があったが、標準C++ライブラリファイルストリームでは、オブジェクトのコンストラクタでファイルストリームを開き、デストラクタで閉じることで、ファイルハンドルの管理を自動化し、リソースリークを防ぐことができる。このようなファイルアクセスの管理に限らず、C++のデストラクタ機構はあらゆるリソースの寿命管理に活用できる。ほかには、マルチスレッドアプリケーションにおいてクリティカルセクションロックの管理にもよく用いられる。C++03規格以前においても、Boost C++ライブラリMicrosoft Foundation Classライブラリなどにクリティカルセクション管理用のRAIIクラスが用意されていたが、C++11規格でスレッドおよび同期オブジェクトが標準化された際に、類似のRAIIクラスがstd::lock_guardおよびstd::unique_lockとして導入された。

また、動的に確保されたメモリの所有権もRAIIで管理できる。所有権が唯一となるスマートポインタクラステンプレートとして、C++03までの標準C++ライブラリではstd::auto_ptrが用意されていたが、C++11以降では非推奨となり、代替のstd::unique_ptrが用意されている。Boost C++ライブラリには類似のクラステンプレートとしてboost::scoped_ptrやboost::interprocess::unique_ptrが実装されている。また、参照カウント方式で所有権を共有するオブジェクトのスマートポインタクラステンプレートとして、Boost C++ライブラリのboost::shared_ptrがある。これはC++11にてstd::shared_ptrとして標準化された。shared_ptrとともに利用する弱参照スマートポインタとして、それぞれboost::weak_ptrおよびstd::weak_ptrが存在する。そのほか、侵入型参照カウント方式のboost::intrusive_ptr、Loki(英語版)のポリシーベースのLoki::SmartPtr、COMインターフェイスオブジェクト (IUnknown) の参照カウント管理に特化したATLのATL::CComPtrなどがある。

後の例のようにRAIIは例外安全の達成にも活用される。RAIIを使えばあちこちにtry-catchブロックを記述することなくメモリリークやリソースリークを防げる。
C++での例

以下、特に断りがない限り、C++03以前でもC++11以降でもコンパイルできるコードで例示する。
動的確保されたメモリの管理

単純な例として、関数内で一時的な作業領域として配列を動的確保することを考える[1]。単純な方法では、以下のようにnew[]演算子を使用する。void function1A(size_t count) { double* array1 = NULL; double* array2 = NULL; try { // 配列を動的に確保する。メモリ確保失敗により std::bad_alloc 例外がスローされる可能性がある。 array1 = new double[count](); array2 = new double[count](); // 動的に確保した配列をここで作業領域として使用する。 for (size_t i = 0; i < count; ++i) { array1[i] = i * 0.1; array2[i] = i * 0.1; } // ... // 配列を使わなくなったので削除する。 delete[] array2; delete[] array1; } catch (...) { // 例外がスローされる場合に備える。 delete[] array2; delete[] array1; throw; // 例外の再送出。 }}

これはC言語のmallocおよびfree関数による原始的な寿命管理手法に近い。もし動的に確保したメモリを削除する前に関数を抜けるとメモリリークしてしまうため、慎重に削除処理をひとつひとつ記述していく必要がある。動的にメモリ管理するオブジェクトの数が増えるにつれ、ソースコードのメンテナンスコストは増大していく。

一方、RAIIを利用した場合は以下のようになる。// RAII を実現する配列ラッパークラス。template<typename T> class ArrayWrapper { size_t m_count; T* m_data;public: ArrayWrapper() : m_count(), m_data() {} explicit ArrayWrapper(size_t count) : m_count(count), m_data(new T[count]()) {} ~ArrayWrapper() { delete[] m_data; } size_t count() const { return m_count; } T* data() { return m_data; } const T* data() const { return m_data; } T& operator[](size_t index) { return m_data[index]; } const T& operator[](size_t index) const { return m_data[index]; } // コピーは禁止とする。所有権の移動もサポートしない。private: ArrayWrapper(const ArrayWrapper&); ArrayWrapper& operator=(const ArrayWrapper&);};void function1B(size_t count) { ArrayWrapper<double> array1(count); ArrayWrapper<double> array2(count); // 動的に確保した配列をここで作業領域として使用する。 for (size_t i = 0; i < count; ++i) { array1[i] = i * 0.1; array2[i] = i * 0.1; } // ...} // RAII 変数 array1, array2 の属するブロックを抜ける。


次ページ
記事の検索
おまかせリスト
▼オプションを表示
ブックマーク登録
mixiチェック!
Twitterに投稿
オプション/リンク一覧
話題のニュース
列車運行情報
暇つぶしWikipedia

Size:24 KB
出典: フリー百科事典『ウィキペディア(Wikipedia)
担当:undef