Introduction to RAII by marduk


Every trade has its tricks. If you have ever worked with carpenters you’ve probably heard the saying, “Measure twice, cut once.” This is, of course, an easy to remember rule that is meant to protect you from your own mistakes. If you measure your cut twice then your chances of making a mistake are much lower because the measurements will probably not match. It’s obviously not a perfect method that’s going to catch everything but if you use it regularly the mistakes that you make will be greatly reduced. Our trade, software development, is no different and each language has its own set of ‘tricks of the trade’ that will help you protect yourself from yourself. Today I will talk about the most important of those tricks to a C++ developer: Resource Acquisition Is Initialization (RAII).

Before I explain what RAII is it is important to know why it is needed. In the development of a software product you will inevitably be required to acquire resources that must then be returned to a pool or service. The most common of these resources is memory, and some languages use what is called “garbage collection” to deal with that resource. However, there are numerous other kinds of resources that you might be required to manage, including such things as database connections, threads, semaphores or mutex locks, file handles, HWNDs, etc. Even trivial uses can quickly become absurdly complicated to correctly manage such resources. The primary method of complication is the introduction of error conditions and error handling. Consider the following basic example:

void fun(size_t buf_size, some_source & handle)
{
char * buf = new char[buf_size];
read(buf, buf_size, handle); // fills the buffer with some data from “handle”
process_data(buf); // does who knows what with the data.
delete buf; // correctly releases the memory resource located at buf.
}

The problem with the above example becomes apparent when we realize that both read() and process_data() may need to account for possible erroneous conditions. In C++, one common way of dealing with that is to throw exceptions. As such, many people erroneously believe that RAII is one of those things we’re forced to learn because of exceptions but this is not the case. The issue still exists even if you use error codes through return values or through some global mechanism like errno(). If we were to modify the above to account for an API that uses return codes for error conditions it might look like so:

int fun(size_t buf_size, some_source & handle)
{
char * buf = new char[buf_size]; 
if (ERROR == read(buf, buf_size, handle))
{
delete buf;
return ERROR;
}
// another little trick is to put the constant first.
// If I accidentally use '=' instead of '==' I get a compiler error.
if (ERROR == process_data(buf))
{
delete buf;
return ERROR;
}
delete buf;
return SUCCESS;
}

It’s starting to get rather repetitious now, isn’t it. As you can see, the problem can quickly become an annoyance just in one basic function. Now imagine having to account for such conditions across multiple calls and with numerous different points of possible error condition where we must make sure that all resources we gathered are appropriately returned to whatever source they were pulled from. What we would like is something that more resembles our first version that didn’t do error handling at all; this is where RAII comes in.

RAII is a technique in which you use the fact that C++ allows you to declare local variables that are automatically created at the point they are defined, and that will then destroyed when they leave scope, no matter how that happens. What RAII means is that your acquisition of resources, no matter what kind, occurs simultaneously with the initialization of such a variable. There are several different ways in which you can do this so lets just go over a few. First, fixing our sample function:

// exception version
void fun(size_t buf_size, some_source & handle)
{
std::vector buf(buf_size);
read(&buf[0], buf_size, handle);
process_data(&buf[0]);
}

First of all, we’ve created our dynamic array of characters by creating a std::vector as a local variable. We provide the size just like before and this operation basically amounts to exactly what we did before except that our buffer is being managed by a local object that will then delete that buffer when it goes out of scope. This is the essence of RAII. The rest is just slight syntactic differences required to get the buffer that the vector allocated (since we’re not actually changing the signatures of the functions we are using).

The important part here is that no matter whether or where an exception is thrown, once we’ve acquired our resource we know it will be deleted because we’ve used RAII to guarantee it. We don’t have to worry about it anymore.

We can use this same technique in our non-exception version:

int fun(size_t buf_size, some_source & handle)
{
std::vector buf(buf_size);
if ( ERROR == read(&buf[0], buf_size, handle)
|| ERROR == process_data(&buf[0]))
return ERROR;
return SUCCESS;
}

What if we’re not trying to allocate an array though? Maybe we’re really dealing with a pointer to some object (because in C++ we of course will have to do this sometimes). What we do in that case is use a “smart pointer”. There are many different smart pointers you can use that will meet different needs (the discussion of which is beyond the scope of this article) but until the next standard comes out the most common of these is the auto_ptr (of course, once the new standard does come out you’ll never want to use auto_ptr again). Here’s an example:

void outer()
{
std::auto_ptr<some_source> my_handle(source_factory::make_source(A_FLAG));
fun(42, *my_handle);
}

Some might be tempted to create the auto_ptr inside of the call to fun(), however in many normal conditions it is possible, because of how C++ specifies function parameter evaluation, for an exception to be thrown between the point where you acquire the resource (the pointer) but before it’s put in an auto_ptr if done within the parameter list of a function call. In More Exceptional C++, Herb Sutter describes the specifics behind this issue and recommends a more strident standard than just using RAII: “Perform every explicit resource allocation (for example, new) in its own code statement, which immediately gives the new’d resource to a manager object (for example, auto_ptr)” (pg 140, An Unmanaged Pointer Problem, Part 2: What about auto_ptr?) In other words, don’t just use RAII, use RAII and also do so in a single statement by itself.

This article has been a very brief overview of RAII and why it is important to know. The managing of resources is often a primary cause of bugs in large software products. Furthermore, very often the kinds of bugs surrounding this issue are the most difficult and time consuming to track down and squash. The use of RAII is a key trick that allows a developer to get responsibly lazy, leaving the language to do the heavy lifting. This is a key skill that any C++ developer is expected to learn and use as a key “best practice”. Remember, RAII is not just about memory but applies to any resource you need to acquire and manage though sometimes you’ll be required to invent your own RAII objects.

If you are reading this article and it is the first time you’ve heard of RAII then I hope I’ve convinced you that you’re going to want to find out more about it. This article is only an introduction to the topic meant to encourage you to expand your knowledge in this area. One place that you can learn more about it is from Bjarne Stroustrup’s book, The C++ Programming Language (section 14.4). Herb Sutter also writes about it in his Exceptional C++ books (along with many other important topics). You can also scour the web for more information and some related “idioms” like the “Scope Guard”, which is a similar construct but uses local object destruction semantics to make sure generic operations are performed at the end of a function. RAII is a very basic, simple concept once learned but most people will need to read a few different sources and practice the skill a few times before fully grasping it.

Author: Noah Roberts, Lacey, Washington in United States