What Is Meant by Resource Acquisition Is Initialization (Raii)

Understanding the meaning of the term and the concept - RAII (Resource Acquisition is Initialization)

So why isn't that called "using the stack to trigger cleanup" (UTSTTC:)?

RAII is telling you what to do: Acquire your resource in a constructor! I would add: one resource, one constructor. UTSTTC is just one application of that, RAII is much more.

Resource Management sucks. Here, resource is anything that needs cleanup after use. Studies of projects across many platforms show the majority of bugs are related to resource management - and it's particularly bad on Windows (due to the many types of objects and allocators).

In C++, resource management is particularly complicated due to the combination of exceptions and (C++ style) templates. For a peek under the hood, see GOTW8).


C++ guarantees that the destructor is called if and only if the constructor succeeded. Relying on that, RAII can solve many nasty problems the average programmer might not even be aware of. Here are a few examples beyond the "my local variables will be destroyed whenever I return".

Let us start with an overly simplistic FileHandle class employing RAII:

class FileHandle
{
FILE* file;

public:

explicit FileHandle(const char* name)
{
file = fopen(name);
if (!file)
{
throw "MAYDAY! MAYDAY";
}
}

~FileHandle()
{
// The only reason we are checking the file pointer for validity
// is because it might have been moved (see below).
// It is NOT needed to check against a failed constructor,
// because the destructor is NEVER executed when the constructor fails!
if (file)
{
fclose(file);
}
}

// The following technicalities can be skipped on the first read.
// They are not crucial to understanding the basic idea of RAII.
// However, if you plan to implement your own RAII classes,
// it is absolutely essential that you read on :)



// It does not make sense to copy a file handle,
// hence we disallow the otherwise implicitly generated copy operations.

FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;



// The following operations enable transfer of ownership
// and require compiler support for rvalue references, a C++0x feature.
// Essentially, a resource is "moved" from one object to another.

FileHandle(FileHandle&& that)
{
file = that.file;
that.file = 0;
}

FileHandle& operator=(FileHandle&& that)
{
file = that.file;
that.file = 0;
return *this;
}
}

If construction fails (with an exception), no other member function - not even the destructor - gets called.

RAII avoids using objects in an invalid state. it already makes life easier before we even use the object.

Now, let us have a look at temporary objects:

void CopyFileData(FileHandle source, FileHandle dest);

void Foo()
{
CopyFileData(FileHandle("C:\\source"), FileHandle("C:\\dest"));
}

There are three error cases to handled: no file can be opened, only one file can be opened, both files can be opened but copying the files failed. In a non-RAII implementation, Foo would have to handle all three cases explicitly.

RAII releases resources that were acquired, even when multiple resources are acquired within one statement.

Now, let us aggregate some objects:

class Logger
{
FileHandle original, duplex; // this logger can write to two files at once!

public:

Logger(const char* filename1, const char* filename2)
: original(filename1), duplex(filename2)
{
if (!filewrite_duplex(original, duplex, "New Session"))
throw "Ugh damn!";
}
}

The constructor of Logger will fail if original's constructor fails (because filename1 could not be opened), duplex's constructor fails (because filename2 could not be opened), or writing to the files inside Logger's constructor body fails. In any of these cases, Logger's destructor will not be called - so we cannot rely on Logger's destructor to release the files. But if original was constructed, its destructor will be called during cleanup of the Logger constructor.

RAII simplifies cleanup after partial construction.


Negative points:

Negative points? All problems can be solved with RAII and smart pointers ;-)

RAII is sometimes unwieldy when you need delayed acquisition, pushing aggregated objects onto the heap.

Imagine the Logger needs a SetTargetFile(const char* target). In that case, the handle, that still needs to be a member of Logger, needs to reside on the heap (e.g. in a smart pointer, to trigger the handle's destruction appropriately.)

I have never wished for garbage collection really. When I do C# I sometimes feel a moment of bliss that I just do not need to care, but much more I miss all the cool toys that can be created through deterministic destruction. (using IDisposable just does not cut it.)

I have had one particularly complex structure that might have benefited from GC, where "simple" smart pointers would cause circular references over multiple classes. We muddled through by carefully balancing strong and weak pointers, but anytime we want to change something, we have to study a big relationship chart. GC might have been better, but some of the components held resources that should be release ASAP.


A note on the FileHandle sample: It was not intended to be complete, just a sample - but turned out incorrect. Thanks Johannes Schaub for pointing out and FredOverflow for turning it into a correct C++0x solution. Over time, I've settled with the approach documented here.

Confusing idiom name RAII

In an RAII type, the constructor acquires the resource that needs to be managed. So, if you do

RAII_Type foo;

Then that resource acquisition happens at initialization, so resource acquisition is initialization.

The meaning of the term - Resource Acquisition Is Initialization

It has been said before (possibly by Scott Meyers, I can't remember), that RAII should be called "Destruction is resource release", or words to that effect.

What "resource acquisition is initialization" literally means is that when an object is constructed (initialized), it acquires some resource (such as a memory allocation or a lock). In other words, it says you should only ever acquire a resource, by initializing some object whose destructor will release it.

This is important to stress because it's a departure from C coding style, where you acquire resources by whatever means a particular API provides (for example malloc(), accept(), or pthread_mutex_lock()), and release them by explicitly calling the corresponding function (for example free(), close(), pthread_mutex_unlock()). The presence of exceptions in C++ makes this approach fairly unworkable. Even in C it results in some tedious code that every use of the API has to write out, and every user has to ensure that control always passes through that code after they're finished using the resource.

But the important part of the pattern is that when the object is destroyed, it releases that resource. It doesn't actually matter whether you acquire the resource by initializing the object, or by doing something else with the object after it has been initialized. And people will still refer to an object as a "RAII object" when there are operations other than initialization that generate the resource(s) managed by the RAII object.

So, don't worry too much about the "acquisition is initialization" in "RAII", because anyway it's slightly misleading.

Why is RAII so named?

First, I should note that it's widely considered a poorly named idiom. Many people prefer SBRM, which stands for Stack Bound Resource Management. Although I (grudgingly) go along with using "RAII" simply because it's widely known and used, I do think SBRM gives a much better description of the real intent.

Second, when RAII was new, it applied as much to the acquisition as releasing of resources. In particular, at the time it was fairly common to see initialization happen in two steps. You'd first define an object, and only afterwards dynamically allocate any resources associated with that object. Many style guides advocated this, largely because at that time (before C++ had exception handling) there was no good way to deal with failure in a constructor. Therefore, the style guides often said, constructors should do only the bare minimum of work, and specifically avoid anything that was open to failure -- especially allocating resources (and a few still say things like that).

Quite a few of those already handled releasing the resources in the destructor though, so that wouldn't have been as clear a distinction from previous practice.

The difference between RAII and smart pointers in C++

RAII is the idea of using C++'s automatic call of a destructor, to release resources acquired in a constructor.

The acronym indicates that only vaguely, Resource Acquisition Is Initialization.

A smart pointer is a class that overloads at least operator-> and the dereference operator* to enable use with pointer notation. Typically a smart pointer will use RAII techniques to automatically deallocate memory. But it can do other things. It is however implicit that a smart pointer deals somehow with ”ownership” of a contained raw pointer. For example, a simple iterator class overloads operator-> and operator* but is not regarded as a smart pointer.

How to deal with Resource-waiting in RAII

RAII means that resources are defined by some object. Ideally, the constructor of that object gets the resource, and the destructor releases it. During the time when the object is valid, you can use the object to interact with the resource.

If a resource "needs to be waited on", then by the rules of RAII, that means you don't have an object that represents that resource yet. You instead have an object that represents a resource that will be available in the future.

Which is why C++ calls this type std::future. It's a template, with the argument being the type of the object whose creation you are waiting on.

Conceptually, a future is just a means to forward an object (or exception) from the piece of code that generates it (possibly asynchronously) to the receiver.

Now given your example, we need to remove initialize from MyClass and make that a function which returns a MyClass instance. It could be a static member of MyClass, or it could be just a namespace-scoped function.

So the code would essentially look like this:

auto future = std::async(initialize);

...

if(future.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
{
MyClass resource = future.get(); //The `future` is now empty. Any exceptions will be thrown here.

//use resource
}


Related Topics



Leave a reply



Submit