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:
1 2 3 4 5 6 7 8 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#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; } |
I get “error: unknown type name ‘constexpr’” on lines 20-21.
compile with -std=c++17 flag; or in VS set language dialect to latest.
I am trying to use the ptr_handle_t pMyClass = new MyClass, but am unable to dereference the pMyClass to get to member functions or data. pMyClass->xxx gives
error C2819: type ‘RAII’ does not have an overloaded member ‘operator ->’
Because it was meant to hold simple system handles etc. You can add operator-> or use unique or shared pointers instead.