c++variadic-templatesraiiresource-managementscopeguard

RAII wrapper for function pairs and template specialization


I've written a RAII wrapper for C function pairs which initialize and release resources and it serves me well for most cases.

#include <GL/glfw.h>
#include <string>
#include <functional>
#include <stdexcept>

template <typename UninitFuncType,
          typename SuccessValueType,
          SuccessValueType successValue>
class RAIIWrapper
{
public:
    template <typename InitFuncType, typename... Args>
    RAIIWrapper(InitFuncType initializer,
                UninitFuncType uninitializer,
                const std::string &errorString,
                const Args&... args) : 
        uninit_func(uninitializer)
    {
        if (successValue != initializer(args...))
            throw std::runtime_error(errorString);
        initialized = true;
    }

    bool isInitialized() const
    {
        return initalized;
    }

    ~RAIIWrapper()
    {
        if (initalized)
            uninit_func();
    }

    // non-copyable
    RAIIWrapper(const RAIIWrapper &) = delete;
    RAIIWrapper& operator=(const RAIIWrapper &) = delete;

private:
    bool initalized = false;
    std::function<UninitFuncType> uninit_func;
};

using GLFWSystem = RAIIWrapper<decltype(glfwTerminate), decltype(GL_TRUE), GL_TRUE>;
using GLFWWindow = RAIIWrapper<decltype(glfwCloseWindow), decltype(GL_TRUE), GL_TRUE>;

int main()
{
    GLFWSystem glfw(glfwInit,
                    glfwTerminate,
                    "Failed to initialize GLFW");
}

However, say when a function returns void like Enter/LeaveCriticalSection I'm not sure how to go about and do it in this class. Should I specialize the class for SuccessValueType = void case? Or something with default template parameter should do?


Solution

  • I'd like to note, that

    1. You do not need information on your initialization function in your wrapper class. You only need to know about uninitialization function.

    2. You can create function helpers to instantiate your wrapper.

    I came up with the following solution (I liked @ipc exception handling idea)

    template <typename UninitF>
    struct RAII_wrapper_type
    {
        RAII_wrapper_type(UninitF f)
        :_f(f), _empty(false)
        {}
        RAII_wrapper_type(RAII_wrapper_type&& r)
        :_f(r._f), _empty(false)
        {
          r._empty = true;
        }
    
        RAII_wrapper_type(const RAII_wrapper_type&) = delete;
        void operator=(const RAII_wrapper_type&) = delete;
    
        ~RAII_wrapper_type()
        {
          if (!_empty) {
            _f();
          }
        }
    
      private:
        UninitF _f;
        bool _empty; // _empty gets true when _f is `moved out` from the object.
    };
    
    template <typename InitF, typename UninitF, typename RType, typename... Args>
    RAII_wrapper_type<UninitF> capture(InitF init_f, UninitF uninit_f, RType succ, 
                                       const char* error, Args... args)
    {
      if(init_f(args...) != succ) {
        throw std::runtime_error(error);
      }
      return RAII_wrapper_type<UninitF>(uninit_f);
    }
    
    template<typename InitF, typename UninitF, typename... Args>
    RAII_wrapper_type<UninitF> capture(InitF init_f, UninitF uninit_f, Args... args)
    {
      init_f(args...);
      return RAII_wrapper_type<UninitF>(uninit_f);
    }
    

    Example:

    void t_void_init(int){}
    int t_int_init(){ return 1; }
    void t_uninit(){}
    
    int main()
    {
      auto t1 = capture(t_void_init, t_uninit, 7);
      auto t2 = capture(t_int_init, t_uninit, 0, "some error");
    }
    

    Edit

    RAII_wrapper_type should have move semantics and we should carefully implement its move constructor to prevent uninit_f from calling several times.