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以降でもコンパイルできるコードで例示する。
動的確保されたメモリの管理
単純な例として、関数内で一時的な作業領域として配列を動的確保することを考える。単純な方法では、以下のようにnew[]演算子を使用する。
これはC言語のmallocおよびfree関数による原始的な寿命管理手法に近い。もし動的に確保したメモリを削除する前に関数を抜けるとメモリリークしてしまうため、慎重に削除処理をひとつひとつ記述していく必要がある。動的にメモリ管理するオブジェクトの数が増えるにつれ、ソースコードのメンテナンスコストは増大していく。
一方、RAIIを利用した場合は以下のようになる。
Boost C ライブラリのboost::scoped_arrayを使う場合は以下のように書ける。
C 11以降のstd::unique_ptrを使う場合は以下のように書ける。
RAIIを使ってメモリ管理する場合、明示的な削除処理の記述が必要なくなり、コードの見通しやメンテナンス性が向上する。関数の途中でreturn文によって脱出したり、例外がスローされたりする場合でも、後始末を自動的に実行してくれる。また、C のテンプレートを利用することで、任意の型に対するRAIIを実現するラッパークラスを定義することができる。C の標準テンプレートライブラリ (STL) には、RAIIの概念をもとに実装された汎用的な動的配列のクラステンプレートとして、std::vectorが用意されている。
コンストラクタからの例外送出とRAII
コンストラクタの実行中、処理が最後まで完了する前に例外がスローされた場合、デストラクタが呼ばれない。そのため、コンストラクタで複数のリソースをnew/new[]してポインタ型のメンバー変数に格納し、デストラクタでdelete/delete[]するようなコードをうかつに書いてしまうとメモリリークの原因となる。
かといって、try-catchを駆使して例外をハンドリングするコードを逐一書いていくと、たちまちソースコードの記述量が膨れ上がってしまう。このような場合は、RAIIクラスをメンバー変数に使うことで簡潔に例外安全を達成できる。
単一のリソースを管理するクラスの場合は、デストラクタで明示的に解放することが許容される。
ファイルハンドルの管理
別の例として、ファイルのオープンとクローズを挙げる。従来の標準Cライブラリを使って、直接リソースを管理する書き方だと以下のようになる。
一方、RAIIを利用した場合は以下のようになる。
標準C ライブラリでは、抽象化されたファイルストリーム管理用のRAIIクラスとして、std::basic_fstreamが用意されている。
FileWrapperクラスではFILE*をカプセル化したが、RAIIの真髄は有限の資源ならば何でも同様に管理できることにある。そしてRAIIでは、関数(やその他ブロック)を抜けるときに適切に資源が破棄されることが保証される。なお、FileWrapperクラスのコンストラクタはファイルが開けなければ例外を投げるため、インスタンスが生成されていれば内包するファイルハンドルは常に利用可能であると仮定してよい。
RAIIを使わない場合、例外が発生するとある問題が生じる。複数の資源を確保する際、それぞれの確保の間に例外が投げられたら、catchブロックではどれを解放すべきか分からなくなってしまう(通常、確保されていない資源を解放することはできない)。function1Aやfunction2Aのように、初期値を無効と見なすことにして二段階初期化したり、try-catchブロックを重ねていったりするなど、状況に応じてコードを適切に書いていかなければ安全性は得られない。function1Bやfunction2BのようなRAIIならばこれにも対処できる。変数(メンバー変数も含む)が構築されたときとは逆順で破棄され、また完全に構築された(内部で例外が投げられずにコンストラクタが実行された)オブジェクトのみが破棄されるので問題は起こらない。これはプログラムが資源(またはそれに類するもの)の管理から逃れることができるようになったということである。RAIIクラスを定義する手間はかかるが、いくつもの関数でRAIIクラスを使っていれば、コードの再利用により全体的にはコードが単純化し、良いプログラムにする手助けとなる。また、副次効果としてビジネスロジックの記述に集中することができるようになる。
なお、function1Aやfunction2AはJavaのようなRAIIでない言語での資源管理に使われるイディオムに似ている。Javaのtry-finallyブロックは資源の確実な返却を実行するポイントを提供するが、都度try-finallyブロックで適切に破棄処理を書かなければならず、プログラマに負担がかかる。
広義のスマートポインタの活用
スマートポインタと呼ばれる類のクラスを使い、RAIIを任意のリソース管理APIへ適用することも可能である。
なお、この節では広い意味でスマートポインタという言葉を使っている。一般的にはメモリに特化したものをスマートポインタと言う。
例えば、stlsoft::scoped_handleは、(voidを含む)任意の型の解放関数を受け付け、また0でない広義の「ヌル」値(無効値)も受け付ける(Windowsのように複数の呼出規約が用いられる環境では、どんなものでも受け付ける)。
以下はWindows APIのファイル入出力関数およびWinsock API関数のリソースをRAIIでラップした例である。CreateFile()関数が返す無効値INVALID_HANDLE_VALUEおよびWSASocket()関数が返す無効値INVALID_SOCKETは、Windows SDK 8.1ではそれぞれ以下のように定義されている。
RAIIは特に複数のリソースを同時に管理する場合に効果を発揮する。少なくともtry-catch節がいくつも現れて混乱する事態からは逃れられる。
制約
RAIIクラスでは解放関数が失敗すると問題になる。C では言語の制約上デストラクタから例外を投げるのは良い考えではないため、デストラクタではすべての例外を握りつぶす必要がある。エラーコードによる通知も難しくなるため、結果として解放失敗の原因を上位層に通知することが難しくなる。そのためstlsoft::scoped_handleのようなクラスは、次のどちらかに当てはまるときには使うべきではない。
- 解放関数が失敗する可能性のある場合
- 利用者がその失敗を知るべき場合
クロージャとRAII
RubyとSmalltalkは特別なスコープに関連付けられた変数の中にあるクロージャブロックという形でRAIIに対応している。以下はRubyの例である。
RAIIに類似した制御構造
C#とVB .NET 2005はC デストラクタに代わるSystem.IDisposableインターフェイスを実装するクラスとusing文を使ってRAIIに似た機能を実現している。
Python 2.5に追加されたwithステートメントでは、同様の目的に__enter__と__exit__のメソッドを使う。
Javaはバージョン7で導入されたtry-with-resources文により、C#のusing文に近い機能を提供する。
脚注
参考文献
- Sutter, Herb (1999). Exceptional C . Addison-Wesley. ISBN 978-0-201-61562-3.
- 日本語訳 ハーブ・サッター 『Exceptional C 』 浜田真理、ピアソンエデュケーション、2000年、249頁。ISBN 978-4-89471-270-6
外部リンク
- Article "The RAII Programming Idiom" by Wikipedia editor Jon Hanna (Talliesin)
- Wiki "Resource Acquisition Is Initialization" from the Portland Pattern Repository
- Sample Chapter "Gotcha #67: Failure to Employ Resource Acquisition Is Initialization" by Stephen Dewhurst
- Article "A Conversation with Bjarne Stroustrup" by Bill Venners
- Article "The Law of The Big Two" by Bjorn Karlsson and Matthew Wilson
- RAII in C by Jonathan Dodds
- Article "Implementing the 'Resource Acquisition is Initialization' Idiom" by Danny Kalev
- Article "RAII" by Mike Nordell
- Article "RAII, Dynamic Objects, and Factories in C " by Roland Pibinger
- Wikibooks RAII in VB6




