Resource acquisition is initialization (RAII)[1] is a programming idiom[2] used in several object-oriented, statically typed programming languages to describe a particular language behavior. In RAII, holding a resource is a class invariant, and is tied to object lifetime. Resource allocation (or acquisition) is done during object creation (specifically initialization), by the constructor, while resource deallocation (release) is done during object destruction (specifically finalization), by the destructor. In other words, resource acquisition must succeed for initialization to succeed. Thus, the resource is guaranteed to be held between when initialization finishes and finalization starts (holding the resources is a class invariant), and to be held only when the object is alive. Thus, if there are no object leaks, there are no resource leaks.
RAII is associated most prominently with C++, where it originated, but also Ada,[3]Vala,[4] and Rust.[5] The technique was developed for exception-saferesource management in C++[6] during 1984–1989, primarily by Bjarne Stroustrup and Andrew Koenig,[7] and the term itself was coined by Stroustrup.[8]
Other names for this idiom include Constructor Acquires, Destructor Releases (CADRe)[9] and one particular style of use is called Scope-based Resource Management (SBRM).[10] This latter term is for the special case of automatic variables. RAII ties resources to object lifetime, which may not coincide with entry and exit of a scope. (Notably variables allocated on the free store have lifetimes unrelated to any given scope.) However, using RAII for automatic variables (SBRM) is the most common use case.
The following example demonstrates usage of RAII for file access and mutex locking:
importstd;usingstd::mutex;usingstd::ofstream;usingstd::runtime_error;usingstd::scoped_lock;usingstd::string;voidwriteToFile(conststring&message){// mutex is to protect access to file (which is shared across threads).staticmutexm;// Lock mutex before accessing file.scoped_lock<mutex>lock(m);// Try to open file.ofstreamf{"example.txt"};if(!f.is_open()){throwruntime_error("unable to open file");}// Write message to file.std::println(f,message);// file will be closed first when leaving scope (regardless of exception)// mutex will be unlocked second (from lock destructor) when leaving scope// (regardless of exception).}This code is exception-safe because C++ guarantees that all objects with automatic storage duration (local variables) are destroyed at the end of the enclosing scope in the reverse order of their construction.[11] The destructors of both the lock and file objects are therefore guaranteed to be called when returning from the function, whether an exception has been thrown or not.[12]
Local variables allow easy management of multiple resources within a single function: they are destroyed in the reverse order of their construction, and an object is destroyed only if fully constructed—that is, if no exception propagates from its constructor.[13]
Using RAII greatly simplifies resource management, reduces overall code size and helps ensure program correctness. RAII is therefore recommended by industry-standard guidelines,[14] and most of the C++ standard library follows the idiom.[15]
The advantages of RAII as a resource management technique are that it provides encapsulation, exception safety (for stack resources), and locality (it allows acquisition and release logic to be written next to each other).
リソース管理ロジックがクラス内で一度だけ定義され、呼び出し箇所ごとに定義されないため、カプセル化が実現されます。スタックリソース(取得時と同じスコープ内で解放されるリソース)の例外安全性は、リソースをスタック変数(特定のスコープ内で宣言されたローカル変数)の有効期間に紐付けることで確保されます。例外がスローされ、適切な例外処理が実装されている場合、現在のスコープを抜ける際に実行されるコードは、そのスコープ内で宣言されたオブジェクトのデストラクタのみです。最後に、クラス定義内でコンストラクタとデストラクタの定義を隣り合わせに記述することで、定義の局所性が確保されます。
したがって、リソース管理は、適切なオブジェクトのライフサイクルと連動させることで、自動的な割り当てと解放を実現する必要があります。リソースは初期化時に取得されるため、利用可能になる前に使用される可能性はなく、同じオブジェクトの破棄によって解放されます。この破棄は、エラーが発生した場合でも確実に実行されます。
RAII をfinallyJava で使用される構造と比較し、Stroustrup は次のように書いています。「現実的なシステムでは、リソースの種類よりもリソースの取得の方がはるかに多いため、『リソースの取得は初期化である』という手法は、『finally』構造を使用するよりもコードが少なくなります。」[ 1 ]
クラス不変条件として、RAII は、リソースを取得したはずのオブジェクト インスタンスが実際にリソースを取得していることを保証します。これにより、新しく作成されたオブジェクトを使用可能な状態にするための追加の「セットアップ」メソッドが不要になります (そのような作業はすべてコンストラクタで実行されます。同様に、リソースを解放する「シャットダウン」タスクはオブジェクトのデストラクタで実行されます)。また、インスタンスが適切にセットアップされていることを毎回使用前に検証する必要もなくなります。[ 16 ]
RAII設計は、マルチスレッドアプリケーションにおけるミューテックスロックの制御によく用いられます。この場合、オブジェクトは破棄される際にロックを解放します。RAIIを使用しない場合、デッドロックが発生する可能性が高く、ミューテックスをロックするロジックとロックを解除するロジックが分離されてしまいます。RAIIを使用すると、ミューテックスをロックするコードには、実行がRAIIオブジェクトのスコープを抜けたときにロックが解放されるというロジックが実質的に含まれます。
もう一つの典型的な例は、ファイルとのやり取りです。書き込み用に開かれたファイルを表すオブジェクトがあるとします。この場合、ファイルはコンストラクタで開かれ、実行がオブジェクトのスコープを抜けるときに閉じられます。どちらの場合も、RAIIは問題のリソースが適切に解放されることを保証するだけであり、例外安全性を維持することには依然として注意が必要です。データ構造やファイルを変更するコードが例外安全でない場合、ミューテックスのロックが解除されたり、データ構造やファイルが破損した状態でファイルが閉じられたりする可能性があります。
動的に割り当てられたオブジェクト(newC++ でメモリが割り当てられたオブジェクト)の所有権も RAII で制御でき、RAII (スタックベース) オブジェクトが破棄されるとオブジェクトが解放されます。この目的のために、C++11 標準ライブラリでは、単一所有オブジェクトと共有所有権を持つオブジェクト用のスマートポインタクラスが定義されています。同様のクラスは、C++98 の およびBoost ライブラリでも利用できます。std::unique_ptrstd::shared_ptrstd::auto_ptrboost::shared_ptr
また、RAII を使用すると、ネットワーク リソースにメッセージを送信できます。この場合、RAII オブジェクトは、コンストラクタの最後に、初期化が完了したときにソケットにメッセージを送信します。また、デストラクタの開始時、つまりオブジェクトが破棄される直前にもメッセージを送信します。このような構造は、クライアント オブジェクトで、別のプロセスで実行されているサーバーとの接続を確立するために使用されることがあります。
直接的なメモリ管理機能を持たない、あるいはその使用を推奨しない多くのプログラミング言語では、「disposeパターン」と呼ばれる同様のメカニズムが用いられ、disposeスコープの終了時にオブジェクトに対して関連するリソースのクリーンアップを実行するメソッドが呼び出されます。
ClangとGNUコンパイラコレクションの導入以前のC言語のバージョンでは、属性をC言語の非標準拡張として実装します。 [ 17 ]次のコードは、変数がスコープ外になったときに呼び出される指定されたデストラクタ関数で変数に注釈を付けます。defer[[gnu::cleanup]]
#include <stdio.h> #include <time.h>void writeLogFile () { const char * logFileName = "logfile.txt" ;[[ gnu :: cleanup ( fclosep )]] FILE * logFile = fopen ( logFileName , "w+" );time_t now = time ( NULL );fprintf ( logFile , "ログの開始時刻: %s、時刻: %s" , filename , ctime ( & now )); }この例では、コンパイラは関数が戻る前にfclosep呼び出されるように調整します。logFilewriteLogFile
C++では、オブジェクトの破棄はデストラクタによって直接行われます。C++では、クラスはスコープの終わりに達するとX自動的にデストラクタを呼び出します。disposeパターンは、C++におけるRAIIとほぼ同等です。~X()
import std ;using std :: ifstream ; using std :: string ;void readFile () { if ( ifstream reader { "story.txt" }; reader ) { string line ; while ( std :: getline ( reader , line )) { std :: println ( "{}" , line ); } } else { std :: println ( stderr , "Failed to open file" ); } // if/elseブロックの後でreaderは破棄されます}C# にusingは、オブジェクトが を実装している場合に使用できる -with-resources ブロックがあります。これは、リソースの末尾にあるメソッドSystem.IDisposableを呼び出します。Dispose()
using System ; using System.IO ;using ( StreamReader reader = new StreamReader ( "story.txt" )) { string line ; while ( ( line = reader.ReadLine ( )) != null ) { Console.WriteLine ( line ); } } //ここでリーダーは自動的に破棄されますJavaにtryは、オブジェクトがを実装している場合に使用できる-with-resourcesブロックがあります。これは、リソースの末尾にあるメソッドjava.lang.AutoCloseableを呼び出します。close()
import java.io.BufferedReader ; import java.io.FileReader ; import java.io.IOException ;try(BufferedReaderreader=newBufferedReader(newFileReader("story.txt"))){Stringline;while((line=reader.readLine())!=null){System.out.println(line);}}catch(IOExceptione){e.printStackTrace();}Python features a with block, which can be used if the object implements __enter__ and __exit__ methods.
withopen("story.txt","r")asfile:print(file.readline())# print the first line in the file2This is also used for managing resources such as locks.
fromthreadingimportLockbalance_lock:Lock=Lock()withbalance_lock:# Critical section: update the account balance here...Rust allows defining custom cleanup logic, if an object implements std::ops::Drop, which will call a drop() method after the object leaves scope. This can also be manually called by std::mem::drop().
RAII only works for resources acquired and released (directly or indirectly) by stack-allocated objects, where there is a well-defined static object lifetime. Heap-allocated objects which themselves acquire and release resources are common in many languages, including C++. RAII depends on heap-based objects to be implicitly or explicitly deleted along all possible execution paths, in order to trigger its resource-releasing destructor (or equivalent).[18]:8:27 This can be achieved by using smart pointers to manage all heap objects, with weak pointers for cyclically referenced objects.
C++ では、スタックの巻き戻しは、例外がどこかでキャッチされた場合にのみ確実に発生します。これは、「プログラム内で一致するハンドラが見つからない場合、terminate() 関数が呼び出されます。terminate() の呼び出し前にスタックが巻き戻されるかどうかは実装定義です (15.5.1)」 (C++03 標準、§15.3/9) によるものです。[ 19 ]オペレーティングシステムはプログラム終了時にメモリ、ファイル、ソケットなどの残りのリソースを解放するため、この動作は通常許容されます。
2018年のGamelabカンファレンスで、Jonathan Blowは、RAIIの使用はメモリ断片化を引き起こし、それがキャッシュミスを引き起こし、パフォーマンスが100倍以上低下する可能性があると主張した。[ 20 ]
Perl、Python(CPython実装)[ 21 ] 、PHP [ 22 ]は、参照カウントによってオブジェクトのライフタイムを管理しており、RAIIの使用を可能にしています。参照されなくなったオブジェクトはすぐに破棄またはファイナライズされて解放されるため、デストラクタまたはファイナライザはその時点でリソースを解放できます。ただし、このような言語では必ずしも慣用的ではなく、特にPythonでは推奨されていません(weakrefパッケージのコンテキストマネージャとファイナライザの使用が推奨されています)。
しかし、オブジェクトのライフタイムは必ずしもスコープに縛られるわけではなく、オブジェクトは非決定的に破棄されるか、まったく破棄されない可能性があります。そのため、スコープの終了時に解放されるべきリソースが意図せずリークする可能性があります。静的変数(特にグローバル変数)に格納されたオブジェクトは、プログラム終了時にファイナライズされない可能性があり、そのリソースは解放されません。たとえば、CPython はそのようなオブジェクトのファイナライズを保証していません。さらに、循環参照を持つオブジェクトは単純な参照カウンタでは回収されず、不定期間存続します。回収されたとしても(より高度なガベージコレクションによって)、破棄のタイミングと破棄の順序は非決定的です。CPython には、サイクルを検出してサイクル内のオブジェクトをファイナライズするサイクル検出器がありますが、CPython 3.4 より前のバージョンでは、サイクル内のオブジェクトにファイナライザがある場合、サイクルは回収されません。[ 23 ]