Resource Acquisition Is Initialization (RAII) is a programming idiom
that describes how resource acquisition and deallocation should be
tied to object lifetime.Wikipedia
demonstrates how the C++ standard library implements this idiom using
the typesstd::lock_guard<>
andstd::ofstream
as examples.But is it really necessary to implement this idiom in every type you
create? Can’t this be achieved in a more aspect-oriented style, i.e.
generically and outside of every class? What if you need to work with
a class from an external library that doesn’t use this idiom?This article aims to provide possible answers to these questions.
Table of Contents
Motivation
There are two more important reasons to use RAII whenever possible that
the Wikipedia article named above fails to mention, at least explicitly:
- Proper usage of this idiom keeps the code for acquisition and
deallocation close together and - it can drastically reduce the amount of repetition needed for deallocation.
Consider the following example:
An external library provides multiple resource classes that don’t
implement the RAII idiom and provide this API:
External library code
class ResourceX final
{
private:
ResourceX();
virtual ~ResourceX();
ResourceX(const ResourceX&) = delete;
ResourceX(ResourceX&&) = delete;
ResourceX& operator=(const ResourceX&) = delete;
ResourceX& operator=(ResourceX&&) = delete;
public:
// Return nullptr on error
static ResourceX* acquire();
static void release(ResourceX*);
// Other functions
// [...]
};
long_function()
is a function in our code that needs to allocate these
resources.
Our code
// Return true on error, false otherwise
bool
long_function()
{
Resource1* r1 = Resource1::acquire();
if (!r1)
{
return true;
}
Resource2* r2 = Resource2::acquire();
if (!r2)
{
Resource1::release(r1);
return true;
}
// [...]
Resource9* r9 = Resource9::acquire();
if (!r9)
{
Resource1::release(r1);
Resource2::release(r2);
// [...]
Resource8::release(r8);
return true;
}
// Resource allocation successful. Actual functionality of long_function
// follows below
// [...]
// Release resources
Resource1::release(r1);
Resource2::release(r2);
// [...]
Resource9::release(r9);
return false;
}
The example demonstrates two issues RAII can help prevent:
- The code for acquisition and deallocation of each resource is spread
throughout the function and - deallocation code must be repeated for every resource, which can
easily be forgotten when the function gets more complex.
Things only get worse if we wanted to have only one return
statement
in our function or deallocation looked different for each resource.
We cannot change the resource classes, because they’re from an external
library, neither can we derive from them, because they’re final, have
deleted copy constructors and assignment operators and we don’t know
what happens in their factory functions. So how can we improve our code
using RAII?
RAII for C++ custom types
The solution is actually pretty simple: We create a mini-class that
holds a reference to the resource whose sole responsibility is to
implement the RAII part that the resource class is missing:
First approach
class Resource1_RAII final
{
public:
Resource1_RAII(Resource1* r1)
: m_r1(r1)
{}
virtual ~Resource1_RAII()
{
Resource1::release(m_r1);
}
private:
Resource1* m_r1;
};
Our code now becomes:
Resource1* r1 = Resource1::acquire();
if (!r1)
{
return true;
}
Resource1_RAII r1_raii(r1);
This already solves our biggest problems: The code for acquisition and
deallocation stays close together and we don’t have to repeat the
deallocation code anymore. We do need to repeat something else though:
The code for our mini-class needs to be copied for each resource. This
is where templates come to the rescue:
Templated approach
template<typename T>
class Resource_RAII final
{
public:
Resource_RAII(T* r1)
: m_r1(r1)
{}
virtual ~Resource_RAII()
{
T::release(m_r1);
}
private:
T* m_r1;
};
Now we don’t need to repeat the code for the mini-classes anymore and
our code looks like this:
Resource1* r1 = Resource1::acquire();
if (!r1)
{
return true;
}
Resource_RAII<Resource1> r1_raii(r1);
This already is a pretty good solution for our current project. But it’s
not quite generic yet. Not every resource is deallocated via a static
release()
function.
Generic templated approach
To get a truly generic RAII mini-class, we need to pass the deallocation
function:
#include <functional>
template<typename T>
class RAII final
{
public:
using DeallocateFunc = ::std::function<void (T*)>;
RAII(T* r1, const DeallocateFunc& df)
: m_r1(r1)
, m_df(df)
{}
virtual ~RAII()
{
m_df(m_r1);
}
private:
T* m_r1;
DeallocateFunc m_df;
};
We now have a RAII mini-class that doesn’t depend on the external
library anymore and is therefor usable in any project context. This
comes at the price of our code becoming a bit more lengthy:
Resource1* r1 = Resource1::acquire();
if (!r1)
{
return true;
}
RAII<Resource1> r1_raii(r1, Resource1::release);
Still, even if we only implement one class template… Isn’t this a use
case that is so typical that there should be a standard solution?
Standard library
Indeed, the C++ standard library already has a plan to provide a generic
solution in the future:
std::scope_exit<>
Until then, we can “abuse” a class that already exists. Experienced C++
programmers may have recognized RAII<>
‘s layout already: It is very
similar to std::unique_ptr<>
! And sure enough, its use as a generic
scope guard is very close to how we’ve written RAII<>
:
Resource1* r1 = Resource1::acquire();
if (!r1)
{
return true;
}
::std::unique_ptr<Resource1, void(*)(Resource1*)> r1_raii(r1, Resource1::release);
Even though its name doesn’t imply its purpose quite as well and the
syntax is a bit irritating at first: This is exactly what we wanted! We
added full RAII support to a class that doesn’t support it and didn’t
even have to write an own class.
If we want to, we can even get back our previous attempt’s short name
and invocation using a type alias:
template<typename T>
using RAII = ::std::unique_ptr<T, void(*)(T*)>;
RAII<Resource1> r1_raii(r1, Resource1::release);
On top of that, we get even more goodies: First, since
std::unique_ptr<>
provides access to its stored pointer, we don’t need
a separate variable anymore and can access the resource through the RAII
class:
RAII<Resource> r1(Resource::acquire(), Resource::release);
if (!*r1)
{
return true;
}
The code for resource acquisition and deallocation are now not only
close together, they’re on the same line!
Until now, we have been thinking about RAII classes as scope guards. But
since std::unique_ptr<>
implements ownership transfer on copy, we can
actually also return the object from our function to transfer the
ownership to the caller. As it turns out, std::unique_ptr<>
is a
full-blown resource management facility included in the C++ standard
library.
RAII for custom types in Python
When programming in Python, we need to worry much less about RAII than
in C++. Python’s standard library is very extensive and uses RAII almost
everywhere. However, there are still some “blind spots” here and there
and external libraries or our own code may still need some proper
treatment in this regard.
For the following example, assume that Python’s built-in open()
function doesn’t support context management (Python’s take on scope
guards). Our code that needs to open two files (this example’s resource)
may look like this:
def long_function():
f1 = None
try:
f1 = open("r", "input.txt")
except OSError:
return True
f2 = None
try:
f2 = open("w", "output.txt")
except OSError:
f1.close()
return True
# Resource allocation successful. Actual functionality of long_function
# follows below
# [...]
f1.close()
f2.close()
return False
The problems are exactly the same as in the C++ example.
We could write a class similar to the one we wrote for C++, but Python’s
solution to this problem are – as hinted above – context managers:
import contextlib
@contextlib.contextmanager
def raii_open(*args, **kwargs):
f = open(*args, **kwargs)
try:
yield f
finally:
f.close()
def long_function():
try:
with raii_open("input.txt", "r") as f1,
raii_open("output.txt", "w") as f2:
# Resource allocation successful. Actual functionality of
# long_function follows below
# [...]
pass
except OSError:
return True
return False
The neat part here is that – on top of solving our RAII issues –
Python’s with
statement allows us to acquire all resources in a single
statement. Only if all resources are allocated successfully the code
enters the respective block.
The way the code is written above comes with a disadvantage, though: Our
actual functionality is now also wrapped in a try - except
block that
is only supposed to apply to the open()
functions. Unfortunately,
there is no “canned” recipe to solve this and the solution depends
heavily on the exact context.
Conclusion
The RAII idiom is used to organize resource allocation lifetime and can
help to reduce code complexity, improve readability and make it more
difficult to break code when functions become longer.
The C++ standard library doesn’t provide a RAII class that is named as
such (yet), but std::unique_ptr<>
surprisingly has everything needed
to be “abused” as a full-blown RAII-style resource manager for objects
that don’t support RAII on their own.
In Python, implementing RAII functionality is much more rarely needed
than in C++. If it is needed, Python’s standard library provides the
contextlib
package to approach the problem.
About the Author
Timo Schmiade is a Principal Software Engineer at e.solutions GmbH.
He is an expert in implementing embedded middleware for automotive infotainment systems in C++.