现在的位置: 首页 > 综合 > 正文

C++ RAII

2013年02月15日 ⁄ 综合 ⁄ 共 7148字 ⁄ 字号 评论关闭

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

抱歉!评论已关闭.