Jump to content

Resource acquisition is initialization

From Wikipedia, the free encyclopedia

This is an old revision of this page, as edited by 71.188.88.67 (talk) at 07:28, 31 May 2012 (added c goto example, removed example of non-RAII code, reworded non-neutral claims about projects failing because of poor C exception handling.). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

Resource Acquisition Is Initialization (RAII, sometimes RIIA) is a programming idiom used in several object-oriented languages like C++, D and Ada. The technique was invented by Bjarne Stroustrup[1] to deal with resource allocation and 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. Resource management therefore needs to be tied to the lifespan of suitable objects in order to gain automatic allocation and reclamation. 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.

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.

The C language does not directly support RAII, though there are some ad-hoc mechanisms available to emulate it. However, some compilers provide non-standard extensions that implement RAII. For example, the "cleanup" variable attribute extension of GCC is one of them.

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 only ensures 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.

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, deprecated in C++11 in favor of std::unique_ptr. Furthermore, smart pointer with shared-ownership semantics such as tr1::shared_ptr (defined in C++ by TR1 and marked for inclusion in the current C++11 standard), or policy based smart pointers such as Loki::SmartPtr (from Loki), can also be used to manage the lifetime of shared objects.

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 file objects 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. Notice that the C++ standard library contains RAII-enabled file I/O in the fstream header and the above file class is only provided as an example of the concept.

C example using GCC extensions

The GNU Compiler Collection implements a non-standard extension to the C language that allows it to support RAII: the "cleanup" variable attribute. For example, 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.

Resource management without RAII

Finalizers

In Java, objects are not allocated on the stack and must be accessed through references; hence, one 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.

In Java versions prior to Java 7, the preceding example would be written like this:

class JavaExample {

    void exampleMethod() {
        // 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();
        }
    }

}

In Java 7 and later, using the new try-with-resources construct, this pattern can be declared a bit more concisely (but is still essentially equivalent to the above code, assuming no exceptions are thrown by the close() method):

class JavaExample {

    void exampleMethod() {
        // open file (acquire resource)
        try (LogFile logfile = new LogFile("logfile.txt")) {
            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
        }
    }

}

(The class used in the statement must implement java.lang.AutoCloseable.) In both cases, the burden of releasing resources falls on the programmer each time a resource is used: The programmer must either explicitly write finally blocks, or must remember to construct the object in the "try-with-resources" construct in order to ensure that the resource is released.

Note, however, that at least one of the benefits of RAII-style coding is still applicable to Java: Construction and initialization can be tightly coupled. For instance, in the above examples, note that it is impossible for code to refer to the variable logfile before it points to a valid object, since the variable logfile is declared explicitly final in the first example (and is implicitly final in the second due to the semantics of the try-with-resources construct) and that in both cases it is initialized on the same line as it is declared, with a new expression that both creates and initializes the object it will reference.

Closure blocks

Ruby and Smalltalk do not support RAII, but have a 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:

(with-open-file (logfile "logfile.txt" :direction :output :if-exists :append)
  (format logfile "hello logfile!"))

Python's with statement and context manager system is also similar and provides deterministic resource management within a block, doing away with the requirement for explicit finally-based cleanup and release.

with open("log.txt", 'r') as logfile:
    logfile.write("hello logfile!")

Before entering the block, the context manager (in this case, a file object) has its __enter__() method called. Before exiting the block (including when this is due to an exception being raised), the context manager's __exit__() method is called.

In C#, the same goal 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", FileMode.OpenOrCreate)) {
            using (StreamWriter log = new StreamWriter(fs)) {
                log.WriteLine("hello logfile!");
            }
        }
    }
}

Visual Basic 2005 also supports the using statement.

Disadvantages of scope bound resource management alternatives

Where both finalizers and closure blocks work as a good alternative to RAII for "shallow" resources, it is important to note however that the compositional properties of RAII differ greatly from these scope bound forms of resource management. Where RAII allows for full encapsulation of resources behind an abstraction, with scope bound resource management this isn't the case. In an environment that purely depends on scope bound resource management, "being a resource" becomes a property that is transitive to composition. That is, using only scope bound resource management, any object that is composed using a resource that requires resource management effectively itself becomes a resource that requires resource management. RAII effectively breaks the transitivity of this property allowing for the existence of "deep" resources to be effectively abstracted away.

Let's for example say we have an object of type A that by composition holds an object of type B that by composition holds an object of type C. Now lets see what happens when we create a new implementation of C that by composition holds a resource R. R will have some close or release method that must be invoked prior to C going out of scope. We could make C into a RAII object for R that invokes release on destruction.

Basically now we have the situation where from the point of view of R and C (shallow resources), scope bound resource management alternatives functionally are fully equivalent to RAII. From a point of view of A and B (deep resources) however, we see a difference in the transitivity of "being a resource" emerging. With C as a RAII object, the interface and implementation of A, B and any code using a scoped A or B will remain unchanged and unaware of the newly introduced existence of "deep" resources. Without RAII however, the fact that C holds R means that C will need its own release method for proxying the release of R. B will now need its own release method for proxying the release of C. A will need its own release method for proxying the release of B, and the scope where either A or B is used will also require of the alternative resource management techniques, where with RAII C provides the abstraction barrier that hides the implementation detail of "implemented using a resource" from the view of A, B and any users of an A or B. This difference shows that RAII effectively breaks the compositional transitivity of the "being a resource" property.

Additionally, the scope-based alternatives do not operate in the same manner as RAII for objects with expression lifetimes. This causes the same lack of transparency mentioned above for object containment with RAII alternatives – that objects must know the resource status of their contained objects – to also be a problem for expressions that return anonymous or temporary objects. Expressions must know if any of their subexpressions return or evaluate to a resource. In languages with RAII, many idioms take advantage of automatic destruction routines, such as expression templates, stream formatters, and function call wrappers. The lack of resource transparency at the expression level means that one cannot write generic or polymorphic code except through promoting all expressions to resources and implementing disposal for expressions that do not themselves have resource semantics. These idioms then lose the benefit of the information hiding and syntactic ease of use that they are often created to provide.

Reference counting

Perl and CPython[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. Circular references can also cause an object's reference count to never drop to zero; another potential memory leak under reference counting. Also, in the case of Python, the actual garbage collection strategy is an implementation detail, and running with an alternative interpreter (such as IronPython or Jython) could result in the RAII implementation not working.

Ad-hoc mechanisms

Languages with no direct support for defining constructors and destructors sometimes still have the ability to support ad-hoc RAII methods using programmer idioms. C, for example, lacks direct support for these features, yet programming languages with such support are often implemented in C, and fragile support can be achieved in C itself, although at the cost of additional cognitive overhead on the part of the programmer.

The lack of any standardized idiom for dynamic object allocation beyond malloc and free can be addressed numerous ways, such as through a programming idiom of having TNew, TDelete, and Tmethod functions for any given type T. The ability of throw to do non-local jumps is analogous to the C-standard setjmp/longjmp library functions, and the syntactic sugar needed to make the result readable can be injected using the C preprocessor. Using variadic macros and in-expression variable declarations makes code like the following possible.

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>

jmp_buf       _jmp_bufs[100]; /* avoid recursion with funcs using try/RAII */
int           _jmp_i = 0;       /* _jmp_bufs[_jmp_i] is the next to use */
const char   *_jmp_text = 0;

/* a variant of Catch without "break;" might be useful for partial cleanups */
#define Try        do{ switch( setjmp(_jmp_bufs[_jmp_i++]) ){ case 0: while(1)
#define Throw(x)       (longjmp(_jmp_bufs[--_jmp_i], x))
#define ThrowText(x,s) (_jmp_text = s, longjmp(_jmp_bufs[--_jmp_i], x))
#define CatchText()    (_jmp_text )
#define Catch(x)       break; case x:
#define Finally        break; default:
#define Done           } }while(0)

enum { ExceptionOpen = 1 /* must be =1 */, ExceptionWrite, ExceptionClose };

/* In GCC, use option "-std=c99"
 * Avoid using 'return', 'break', 'goto' to escape RAII blocks early!
 * Without the exception handling, this macro would be simpler:
 *   #define RAII(t, v, a...) for(t *v = t ## New(a, __VA_ARGS__) ; v ; t ## Delete(&v))
 */
#define RAII(t, v, a,...) \
    for(int _outer_once = 1, _inner_once = 1, _jumped = 0 ; _outer_once-- ; \
        _jumped && (Throw(_jumped), 1), 1)                              \
        for(_jumped = setjmp(_jmp_bufs[_jmp_i++]) ; _inner_once-- ; )   \
            for( t *v = t ## New(a, __VA_ARGS__) ; v ; t ## Delete(&v))

typedef struct _File { FILE *fp; } File;

void FileDelete(File **faddr) {
    if((*faddr)->fp)
        if(EOF == fclose((*faddr)->fp))
            Throw(ExceptionClose);
    free(*faddr);
    *faddr = 0;
}

File *FileNew(const char *pathname, char *mode) {
    File *f;
    if(f = malloc(1,sizeof(FILE))) {
        if( ! (f->fp = fopen(pathname, mode))) {
            FileDelete(&f);
            ThrowText(ExceptionOpen, pathname);
        }
    }

    return f;
}

void FilePutc(File *f, int c) {
    if(EOF == fputc(c, f->fp)) Throw(ExceptionWrite);
}

int FileGetc(File *f) { return fgetc(f->fp); }

int main(int ac, char **av) {
    Try {
        RAII(File, from, "file-from", "r+") { /* r+ to ban opening dirs */
            RAII(File, to, "file-to", "w") {
                int c;
                while(EOF != (c = FileGetc(from)))
                    FilePutc(to, c);
                puts("copy complete");
            }
        }
    }
    Catch(ExceptionOpen)  { printf(">>> open error on \"%s\"\n", CatchText()); }
    Catch(ExceptionClose) { puts(">>> close error!"); }
    Catch(ExceptionWrite) { puts(">>> write error!"); }
    Finally { puts("finally :-)"); } Done;
    return 0;
}


The use of goto,[3] is a common C idiom, used notably in the Linux kernel to do RAII. Each label acts as an entry-point into the chain of deinitialization that takes place at the end of the function, preventing not-yet-allocated resources from being released. At the cost of verbosity and potential repetition, it avoids exceptions, dynamic memory, and ad-hoc object systems.

#include <stdio.h>
 
int main(int ac, char **av) {
    int ret = EXIT_FAILURE;
    FILE *from, *to;

    if((from = fopen("file-from", "r+"))==NULL) 
        goto from_failed;
    if((to = fopen("file-to", "w"))==NULL)
        goto to_failed;

    int c;
    while(EOF != (c = fgetc(from))) {
        if(EOF == fputc(c, to)) 
            goto putc_failed;
    }
    puts("copy complete");
 
success:
    ret = EXIT_SUCCESS;
putc_failed:
    fclose(to);
to_failed:
    fclose(from);
from_failed:
 
    return ret;
}

There are many different, often partial, solutions to the problem, such as handling object cleanup with goto, code that creates objects for the exceptions,[4] or addresses multithreading issues.[5] The real weakness of not having direct language support for RAII features is in situations where code from multiple sources is being combined, and all the ad-hoc methods being used fail to interact properly, or worse yet, directly conflict with each other.

Even where mixing with other ad-hoc approaches can be obviated, there is still an issue of questionable code resilience. Ad-hocs are typically specific to individual projects, and usually haven't been subjected to the same rigorous testing and code review that RAII and exception mechanisms are when supported as a primary language feature. A project relying heavily these types of patterns might reasonably use the exception handling of an internationally supported C++ compiler, rather than an ad-hoc C mechanism such as the example provided above, or even mechanisms constructed in house by a single team.

References

  1. ^ Stroustrup, Bjarne (1994). The Design and Evolution of C++. Addison-Wesley. ISBN 0-201-54330-3.
  2. ^ Python 2.5.2 manual: 1.10 Reference Counts Accessed on 2009-05-15.
  3. ^ CERT MEM12-C. Consider using a Goto-Chain when leaving a function on error when using and releasing resources
  4. ^ Exceptions in C
  5. ^ Exception Handling in C without C++