Jump to content

Resource acquisition is initialization

From Wikipedia, the free encyclopedia

This is an old revision of this page, as edited by Kvangend (talk | contribs) at 11:18, 30 October 2013 (→‎C++ example: add a note that this this C++11 code - std::lock_guard doesn't exist in earlier versions of the standard.). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

Resource Acquisition Is Initialization (RAII)[1] is a programming idiom used in several object-oriented languages like C++, D, Ada and Vala. The technique was invented by Bjarne Stroustrup[2] to deal with resource allocation and deallocation in C++. In this language, if an exception is thrown, and proper exception handling is in place, the only code that will be executed for the current scope are the destructors of objects declared in that scope. Resource management therefore needs to be tied to the lifespan of suitable objects in order to gain automatic allocation and reclamation. Resources 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.

Typical uses

The RAII technique is often used for controlling mutex locks in multi-threaded applications. In that use, the object releases the lock when destroyed. Without RAII in this scenario the potential for deadlock would be high and the logic to lock the mutex would be far from the logic to unlock it. With RAII, the code that locks the mutex essentially includes the logic that the lock will be released when the RAII object goes out of scope.

Another typical example is interacting with files: We could have an object that represents a file that is open for writing, wherein the file is opened in the constructor and closed when the object goes out of scope. In both cases, RAII ensures only that the resource in question is released appropriately; care must still be taken to maintain exception safety. If the code modifying the data structure or file is not exception-safe, the mutex could be unlocked or the file closed with the data structure or file corrupted.

Ownership of dynamically allocated objects (memory allocated with new in C++) can also be controlled with RAII, such that the object is released when the RAII object is destroyed. For this purpose, the C++11 standard library defines the smart pointer classes std::unique_ptr for single-owned objects and std::shared_ptr for objects with shared ownership. Similar classes are also available through std::auto_ptr in C++98, and boost::shared_ptr in the Boost libraries.

C++ example

The following C++11 example demonstrates usage of RAII for file access and mutex locking:

#include <string>
#include <mutex>
#include <iostream>
#include <fstream>
#include <stdexcept>

void write_to_file (const std::string & message) {
    // mutex to protect file access
    static std::mutex mutex;

    // lock mutex before accessing file
    std::lock_guard<std::mutex> lock(mutex);

    // try to open file
    std::ofstream file("example.txt");
    if (!file.is_open())
        throw std::runtime_error("unable to open file");
    
    // write message to file
    file << message << std::endl;
    
    // file will be closed 1st when leaving scope (regardless of exception)
    // mutex will be unlocked 2nd (from lock destructor) when leaving
    // scope (regardless of exception)
}

This code is exception-safe because C++ guarantees that all stack objects are destroyed at the end of the enclosing scope, known as stack unwinding. 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.[3]

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.[4]

Using RAII greatly simplifies resource management, reduces overall code size and helps ensure program correctness. RAII is therefore highly recommended in C++, and most of the C++ standard library follows the idiom.[5]

Resource management without RAII

The dispose pattern

Many garbage-collected languages, including Java, C# and Python, support various forms of the dispose pattern to simplify cleanup of resources. This results in simpler code and can serve as an alternative to RAII for "shallow" resources.

The compositional properties of RAII, however, differ significantly from scope bound resources in that it allows for full encapsulation of the resources behind an abstraction. With the dispose pattern for resource management, "being a resource" becomes a property that is transitive to composition. That is, any object that is composed using a resource that requires resource management effectively itself becomes a resource that requires resource management.

This limitation is typically encountered whenever developing custom classes. Custom classes in C# and Java have to explicitly implement the dispose method in order to be dispose-compatible for the client code. The dispose method has to contain explicit closing of all child resources belonging to the class. This limitation does not exist in C++ with RAII, where the destructor of custom classes automatically destructs all child resources recursively without requiring any explicit code.

Reference counting

Perl, CPython[6] and PHP[7] manage object lifetime by reference counting, which makes it possible to use RAII. Objects that are no longer referenced are immediately released, so a destructor can release the resource at that time.

However, object lifetimes aren't necessarily bound to any lexical scope. Objects stored as a global variable and objects with circular references will for instance live indeterminately long. This makes it possible to accidentally leak resources that should have been released at the end of some scope. Also, in the case of Python, the garbage collection strategy is an implementation detail, so running with an alternative interpreter (such as IronPython or Jython) could result in the RAII implementation not working.

GCC extensions for C

The GNU Compiler Collection implements a non-standard extension to the C language to support RAII: the "cleanup" variable attribute. The following macro defines a variable with a given type and attaches a function to it that will be called when the variable goes out of scope:

#define RAII_VARIABLE(vartype,varname,initval,dtor) \
    void _dtor_ ## varname (vartype * v) { dtor(*v); } \
    vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)

This macro can then be used as follows:

void example_usage() {
  RAII_VARIABLE(FILE*, logfile, fopen("logfile.txt", "w+"), fclose);
  fputs("hello logfile!", logfile);
}

In this example, the compiler arranges for the fclose function to be called before example_usage returns.

References

  1. ^ Bjarne Stroustrup Why doesn't C++ provide a "finally" construct? Accessed on 2013-01-02.
  2. ^ Stroustrup, Bjarne (1994). The Design and Evolution of C++. Addison-Wesley. ISBN 0-201-54330-3.
  3. ^ "dtors-shouldnt-throw". Retrieved 12 February 2013.
  4. ^ "What's the order that local objects are destructed?". Retrieved 12 February 2013.
  5. ^ "too-many-trycatch-blocks". Retrieved 12 February 2013.
  6. ^ Python 2.5.2 manual: 1.10 Reference Counts Accessed on 2009-05-15.
  7. ^ http://stackoverflow.com/a/4938780