RAII, AC/DC, and the “with” statement
February 2009
Once upon a time there was a programming idiom called Resource Acquisition Is Initialization (RAII), and it almost deserved it. (Apologies to C. S. Lewis.)
First, a bit of context
Recently I wanted to figure out exactly what this RAII thing was that C++ and a few other languages have. With such a name, I figured it must be both extremely powerful and rather complicated.
What does “resource acquisition is initialization” even mean? Does it matter if you call it “initialization is resource acquisition”? And why initialization when everyone seems to think it’s mostly about what to do on destruction?
Well, it turns out it’s badly named. Even the C++ FAQ agrees:
However, if you dissect “RAII” as an acronym, and if you look (too?) closely at the words making up that acronym, you will realize that the words are not a perfect match for the concept. Who cares?!? The concept is what’s important; “RAII” is merely a moniker used as a handle for that concept.
Who cares? Maybe just me, but usually an acronym tells you what something is. What’s called RAII is actually a fairly simple concept, and perhaps more of us would use and understand it if it had a better name. So I hereby propose one:
Enter AC/DC
AC/DC — Acquire in Constructor, Destructor does Cleanup.
First AC/DC was two types of electrical current, then a heavy-metal band. And now it’s a C++ programming idiom. :-) The idea is to acquire or allocate all the resources you need in a class’s constructor, and clean them up or free them in the destructor.
My first thought when I’d figured out what RAII meant was, “Is that all? Isn’t that what everyone does anyway?” Well, yes and no.
For a start, this works in C++ because destructors are called in a deterministic way. The destructor is called when an object goes out of scope — and this is one of the key points — even when an exception occurs.
So when you use AC/DC, your resources are tied to objects, and when and however the objects die, the resources are freed too.
Well, how do you use it, and what’s it good for? Let’s see an example:
The old FILE-wrapper example
#include <cstdio>
#include <exception>
class CharReader {
public:
class Exception : public std::exception {
public:
virtual const char* what() const throw() { return "Error!"; }
};
static const int EndOfFile = EOF;
CharReader(const char* name) {
f = fopen(name, "r");
if (!f)
throw Exception();
}
~CharReader() {
printf("Closing file\n");
fclose(f);
}
int Read() {
int c = fgetc(f);
if (c == EOF && ferror(f))
throw Exception();
return c;
}
private:
FILE* f;
};
int main() {
int c;
try {
CharReader reader("CharReader.cpp");
while ((c = reader.Read()) != CharReader::EndOfFile) {
if (c == 'z') {
printf("'z' byte found in file!\n");
return 2;
}
printf("%02X ", c);
}
} catch (CharReader::Exception& e) {
printf("Error: %s\n", e.what());
return 1;
}
return 0;
}
How it works
As soon as you instantiate the reader
it opens the file. The object owns the file until it goes out of scope and the destructor is called. However the program exits (with an early return or via an exception) the reader’s destructor is called and the file closed.
In this case it’s not a big deal (the OS will close files for you anyway), but when you’re writing a file and haven’t flushed, or when you’re connecting to a database and need to commit, or when you’ve disabled interrupts and need to re-enable them regardless of where the function return
s — these are great uses for the AC/DC idiom.
Python’s “with” statement
Python (and Java, I think) doesn’t really do AC/DC, because when an object goes out of scope, its __del__
method isn’t necessarily called. __del__
is only called when the object’s reference count goes down to zero or when the object is collected as garbage.
The old-school way of coping with this was try ... finally
, for example:
def log(s):
f = open('output.log', 'a+')
try:
f.write(s + '\n')
finally:
f.close()
That way, whether or not a write exception occurred, your file would be closed and the log flushed. But with the introduction of the with
statement in Python 2.5 you can do what amounts to AC/DC using with
:
def log(s):
with open('output.log', 'a+') as f:
f.write(s + '\n')
The difference is that it’s explicit, rather than implicit based on the scope of the object. (The with
protocol uses the special functions __enter__
and __exit__
rather than the constructor and destructor.)
References
Some further reading if you’re interested:
- An explanation of RAII from the Firebird docs
- Exception Safety, a paper by Stroustrup that talks about RAII [pdf]
- Effective C++, a book by Scott Meyers that discusses RAII in depth
Comments
Eric Larson 7 Feb 2009, 11:47
Jesse Noller wrote a good article on the with statement and context managers[1]. I also wrote a simple connection pool using context managers based on his article.[2]
[1] http://jessenoller.com/2009/02/03/get-with-the-program-as-contextmanager-completely-different/
[2] http://ionrock.org/blog/2009/02/05/A_Contextmanager_Based_Connection_Pool
Ben 7 Feb 2009, 13:10
Thanks, Eric — that’s useful stuff.