I know the topic of RAII has been blogged about plenty of times before. Still, I want to present to you my take on it . Recently, I created a policy-based generic RAII template for holding various types of resources (pointers, file handles, mutexes, etc.). The nice thing about my implementation is that in order to acquire and release a new type of a resource, you only have to define simple Acquire
and Release
policy template classes and plug them as parameters to the RAII
template. Here’s how you can use it with std::mutex
:
template<typename T> struct LockPolicy { static void Execute(T t) { t.lock(); } };
template<typename T> struct UnlockPolicy { static void Execute(T t) { t.unlock(); } };
template<typename T> using scope_lock = RAII<T&, LockPolicy, UnlockPolicy>;
std::mutex m;
{
scope_lock<std::mutex> lock(m);
}
In the code above, we declare 2 policy classes: LockPolicy
which calls .lock();
on type T
, and UnlockPolicy
which calls .unlock();
on type T
. Next, we declare scope_lock
to be a template which will hold type T
by reference and apply LockPolicy::Execute(T t);
and UnlockPolicy::Execute(T t);
in the constructor and destructor of RAII
. This way, we can use scope_lock
with any object that has .lock();
and .unlock();
methods.
As an exercise in writing policy classes, let’s use RAII
template to hold and automatically delete
or delete[]
pointers and pointers to arrays:
template<typename T> struct NoOpPolicy { static void Execute(T) {} };
template<typename T> struct PointerReleasePolicy { static void Execute(T ptr) { delete ptr; } };
template<typename T> struct ArrayReleasePolicy { static void Execute(T ptr) { delete[] ptr; } };
template<typename T> using ptr_handle_t = RAII<T*, NoOpPolicy, PointerReleasePolicy>;
template<typename T> using arr_ptr_handle_t = RAII<T*, NoOpPolicy, ArrayReleasePolicy>;
{
ptr_handle_t<int> p1 = new int;
arr_ptr_handle_t<int> p2 = new int [2];
*p1 = 0xDEADBEEF;
p2[1] = 0x8BADF00D;
}
First, we need a policy that does nothing; let’s call it NoOpPolicy
. That is because nothing needs to happen to a pointer in the constructor of RAII
; it’s already allocated. Next, we declare two policies: PointerReleasePolicy
which calls delete
on type T
, and ArrayReleasePolicy
which calls delete[]
on type T
. Finally, we define ptr_handle_t
to be a RAII
template which holds a pointer to T
, applies NoOpPolicy::Execute(T t);
in its constructor and PointerReleasePolicy::Execute(T t);
in its destructor. We do the same for arr_ptr_handle_t
except using ArrayReleasePolicy
.
Complete Listing
#include <mutex>
template<
typename T,
template<typename> typename AcquirePolicy,
template<typename> typename ReleasePolicy
>
class RAII
{
public:
typedef T val_type;
typedef T& ref_type;
RAII(val_type h) : m_handle(h) { AcquirePolicy<T>::Execute(m_handle); }
RAII(const RAII&) = delete;
RAII(RAII&&) = delete;
RAII& operator = (const RAII&) = delete;
~RAII() { ReleasePolicy<T>::Execute(m_handle); }
constexpr operator ref_type () { return m_handle; }
constexpr operator ref_type () const { return m_handle; }
private:
val_type m_handle;
};
template<typename T> struct LockPolicy { static void Execute(T t) { t.lock(); } };
template<typename T> struct UnlockPolicy { static void Execute(T t) { t.unlock(); } };
template<typename T> using scope_lock = RAII<T&, LockPolicy, UnlockPolicy>;
template<typename T> struct NoOpPolicy { static void Execute(T) {} };
template<typename T> struct PointerReleasePolicy { static void Execute(T ptr) { delete ptr; } };
template<typename T> struct ArrayReleasePolicy { static void Execute(T ptr) { delete[] ptr; } };
template<typename T> using arr_ptr_handle_t = RAII<T*, NoOpPolicy, ArrayReleasePolicy>;
template<typename T> using ptr_handle_t = RAII<T*, NoOpPolicy, PointerReleasePolicy>;
int main(int argc, char** argv)
{
std::mutex m;
scope_lock<std::mutex> lock(m);
ptr_handle_t<int> p1 = new int;
arr_ptr_handle_t<int> p2 = new int [2];
*p1 = 0xDEADBEEF;
p2[1] = 0x8BADF00D;
return 1;
}