Resource Acquisition Is Initialization
From Wikipedia, the free encyclopedia
|
This article may require cleanup to meet Wikipedia’s quality standards. Please improve this article if you can. (March 2007) |
Resource Acquisition Is Initialization, often referred to by the acronym RAII, is a popular design pattern in several object oriented programming languages like C++, D and Ada. The technique was invented by Bjarne Stroustrup[1] to deal with resource deallocation in C++. In this language, the only code that can be guaranteed to be executed after an exception is thrown are the destructors of objects residing on the stack. Resources therefore need to be tied to the lifespan of suitable objects. They are acquired during initialization, when there is no chance of them being used before they are available, and released with the destruction of the same objects, which is guaranteed to take place even in case of errors.
RAII is vital in writing exception-safe C++ code: to release resources before permitting exceptions to propagate (in order to avoid resource leaks) one can write appropriate destructors once rather than dispersing and duplicating cleanup logic between exception handling blocks that may or may not be executed.
Contents[hide] |
[edit] Language support
C++ and D allow objects to be allocated on the stack and their scoping rules ensure that destructors are called when a local object’s scope ends. By putting the resource release logic in the destructor, C++’s and D’s scoping provide direct support for RAII.
[edit] Typical uses
The RAII technique is often used for controlling mutex locks in multi-threaded applications. In that use, the object releases the lock, if held, when destroyed. Without RAII in this scenario the potential for deadlock would be high. Another typical example is file management, wherein the file class closes the associated file, if open, when destroyed.
The ownership of dynamically allocated memory (such as memory allocated with new in C++ code) can be controlled with RAII, such that the memory is released when the RAII object is destroyed. For this purpose, the C++ Standard Library defines the smart pointer class std::auto_ptr. Furthermore, smart pointer with shared-ownership semantics such as tr1::shared_ptr
(defined in C++ by TR1 and marked for inclusion in the new C++0x standard), or policy based smart pointers such as Loki::SmartPtr
(from Loki), can also be used to manage the lifetime of shared objects.
[edit] C++ example
The following RAII class is a lightweight wrapper of the C standard library file system calls.
#include <cstdio> #include <stdexcept> // std::runtime_error class file { public: file (const char* filename) : file_(std::fopen(filename, "w+")) { if (!file_) { throw std::runtime_error("file open failure"); } } ~file() { if (std::fclose(file_)) { // failed to flush latest changes. // handle it } } void write (const char* str) { if (EOF == std::fputs(str, file_)) { throw std::runtime_error("file write failure"); } } private: std::FILE* file_; // prevent copying and assignment; not implemented file (const file &); file & operator= (const file &); };
The class file
can then be used as follows:
void example_usage() { file logfile("logfile.txt"); // open file (acquire resource) logfile.write("hello logfile!"); // continue using logfile ... // throw exceptions or return without // worrying about closing the log; // it is closed automatically when // logfile goes out of scope }
This works because the class file
encapsulates the management of the FILE*
file handle. When objects file
are local to a function, C++ guarantees that they are destroyed at the end of the enclosing scope (the function in the example), and the file
destructor releases the file by calling std::fclose(file_)
. Furthermore, file
instances guarantee that a file is available by throwing an exception if the file could not be opened when creating the object.
Local variables easily manage multiple resources within a single function: They are destroyed in the reverse order of their construction, and an object is only destroyed if fully constructed. That is, if no exception propagates from its constructor.
Using RAII-enabled resources simplifies and reduces overall code size and helps ensure program correctness.
[edit] Resource management without RAII
In Java, objects are not allocated on the stack and must be accessed through references; hence you cannot have automatic variables of objects that “go out of scope”. Instead, all objects are dynamically allocated. In principle, dynamic allocation does not make RAII unfeasible per se; it could still be feasible if there were a guarantee that a “destructor” (“finalize”) method would be called as soon as an object were pointed to by no references (i.e., if the object lifetime management were performed according to reference counting).
However, Java objects have indefinite lifetimes which cannot be controlled by the programmer, because, according to the Java Virtual Machine specification, it is unpredictable when the garbage collector will act. Indeed, the garbage collector may never act at all to collect objects pointed to by no references. Hence the “finalize” method of an unreferenced object might never be called or be called long after the object became unreferenced. Resources must thus be closed manually by the programmer, using something like the dispose pattern.
The preceding example would be written like this:
void java_example() { // open file (acquire resource) final LogFile logfile = new LogFile("logfile.txt"); try { logfile.write("hello logfile!"); // continue using logfile ... // throw exceptions or return without worrying about closing the log; // it is closed automatically when exiting this block } finally { // explicitly release the resource logfile.close(); } }
The burden of releasing resources falls on the programmer each time a resource is used.
Ruby and Smalltalk do not support RAII, but have a simpler and more flexible pattern that makes use of methods that pass resources to closure blocks. Here is an example in Ruby:
File.open("logfile.txt", "w+") do |logfile| logfile.write("hello logfile!") end
The open
method ensures that the file handle is closed without special precautions by the code writing to the file. This is similar to Common Lisp‘s ‘unwind-protect’-based macros.
Python‘s ‘with’ statement and the ‘using’ statement in C# and Visual Basic 2005 provide deterministic resource management within a block and do away with the requirement for explicit finally
-based cleanup and release.
In C# this is accomplished by wrapping any object that implements the IDisposable interface in a using statement. When execution leaves the scope of the using statement body the Dispose method on the wrapped object is executed giving it a deterministic way to clean up any resources.
public class CSharpExample { public static void Main() { using(FileStream fs = new FileStream("log.txt")) using(StreamWriter log = new StreamWriter(fs)) { log.WriteLine("hello logfile!"); } } }
Perl and Python[2] manage object lifetime by reference counting, making it possible to use RAII in a limited form. Objects that are no longer referenced are immediately released, so a destructor can release the resource at that time. However, object lifetime isn’t necessarily bound to any lexical scope. One can store a reference to an object in a global variable, for example, thus keeping the object (and resource) alive indeterminately long. This makes it possible to accidentally leak resources that should have been released at the end of some scope.
C requires significant administrative code since it doesn’t support exceptions, try-finally blocks, or RAII at all. A recommended approach[3] is to separate releasing of resources at the end of the function and jump there with gotos in the case of error. This way the cleanup code need not be duplicated.
int c_example() { int ret = 0; // return value 0 is success FILE *f = fopen("logfile.txt", "w+"); if (!f) { ret = -1; goto fail_fopen; } if (fputs("hello logfile!", f) == EOF) { ret = -2; goto fail_fputs; } // continue using the file resource // Releasing resources (in reverse order) fail_fputs: if (fclose(f) == EOF) { ret = -3; } fail_fopen: return ret; }
Variations exist, but the example illustrates the general approach.
今天的文章C++ RAII分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/10323.html