Resource Acquisition Is Initialization

From Wikipedia, the free encyclopedia

Jump to: navigation, search

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, invented by Bjarne Stroustrup[1], ensures that when resources are acquired they are properly released by tying them to the lifespan of suitable objects: resources are acquired during the initialization of objects, when there is no chance of using them before the resource is available, and released with the destruction of the same objects, which is guaranteed to take place even in case of errors.

In C++, objects residing on the stack are automatically destroyed when the enclosing scope is exited, including the case of exceptions [2]; their destructor is called before the exception propagates.

RAII helps in writing exception-safe 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.

Contents

[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 thread locks in multi-threaded applications. In that use, the object releases the lock, if held, when destroyed. 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 boost::shared_ptr (defined in C++ by Boost 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 lifetime of shared objects.

[edit] Mutability (C++)

In C++, an important consideration of classes that implement RAII is their mutability[3]. A class exhibiting mutable RAII provides facilities for instances to be assigned a new resource; one that exhibits immutable RAII does not. An example of the former is std::auto_ptr; examples of the latter are the STLSoft library's stlsoft::scoped_handle and Boost.SmartPtr's boost::scoped_ptr.

Classes exhibiting immutable RAII are considerably easier to implement in C++, since the design need not account for assignment (including copy-assignment), except to explicitly prohibit it. Consider the following RAII class template that illustrates the simplicity of implementing immutable RAII. Compare this with the implementation of std::auto_ptr or another mutable RAII smart pointer, which will have many more member functions and logic to handle premature release in cases of self-assignment.

template <typename T>
class immutable_scoped_ptr
{
public:
    explicit immutable_scoped_ptr(T* t) throw()
        : t_(t)
    { }
 
    ~immutable_scoped_ptr() throw() { delete t_; }
 
    T& operator *() const throw() { return *t_; }
 
    T* operator ->() const throw() { return t_; }
 
private:
    T* const t_;
 
    // prevent copying and assignment; not implemented
    immutable_scoped_ptr(const immutable_scoped_ptr&);
    immutable_scoped_ptr& operator= (const immutable_scoped_ptr&);
};

[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 (0 != 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 &);
};

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 even never act at all to collect objects pointed to by no references. Hence the "finalize" method of an unreferenced object might be never called or called only long after the object became unreferenced. Resources must thus be closed manually by the programmer.

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.

Perl manages 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 of any kind. A typical approach 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 retval = 0; // return value 0 is success
    FILE *f = fopen("logfile.txt", "w+");
    if (!f) {
        retval = -1;
        goto bailout1;
    }
    if (fputs("hello logfile!", f) == EOF) {
        retval = -2;
        goto bailout2;
    }
 
    // continue using the file resource
 
    // Releasing resources (in reverse order)
  bailout2:
    if (fclose(f) == EOF) {
        retval = -3;
    }
 
  bailout1:
    return retval;
}

Variations exist, but the example illustrates the general approach.

[edit] References

  1. ^ Stroustrup, Bjarne (1994). The Design and Evolution of C++. Addison-Wesley. ISBN 0-201-54330-3. 
  2. ^ Bjarne Stroustrup (April 2001) (PDF). Exception Safety: Concepts and Techniques. http://www.research.att.com/~bs/except.pdf. Retrieved on 2007-09-02. 
  3. ^ Wilson, Matthew (2004). Imperfect C++. Addison-Wesley. ISBN 0-321-22877-4. 

[edit] External links

Personal tools