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?
The principle that objects own resources is also known as "resource acquisition is initialization," or RAII. When a resource-owning stack object goes out of scope, its destructor is automatically invoked. In this way, garbage collection in C++ is closely related to object lifetime, and is deterministic.
RAII is an important C++ idiom for resource management. Notably, RAII provides a structural idiom for proper resource management with exceptions. The power of the idiom is in the guarantees it provides. Properly used, the destructor for your RAII-object is guaranteed to be called to allow you to free resources.
Resource acquisition is initialization (RAII) is a programming idiom used in several object-oriented, statically-typed programming languages to describe a particular language behavior.
Resource Acquisition Is Initialization or RAII, is a C++ programming technique which binds the life cycle of a resource that must be acquired before use (allocated heap memory, thread of execution, open socket, open file, locked mutex, disk space, database connection—anything that exists in limited supply) to the ...
I'd like to note, that
You do not need information on your initialization function in your wrapper class. You only need to know about uninitialization function.
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.
I would seperate the logic of return-Checking and of RAII-Wrapping
template <typename UninitFuncType>
class RAIIWrapper
{
public:
template <typename InitFuncType, typename... Args>
RAIIWrapper(InitFuncType fpInitFunc,
UninitFuncType fpUninitFunc,
Args&&... args)
: fpUninit(std::move(fpUninitFunc))
{
static_assert(std::is_void<decltype(fpInitFunc(args...))>::value, "ignored return value");
fpInitFunc(std::forward<Args>(args)...);
}
bool isInitialized() const { return true; } // false is impossible in your implementation
~RAIIWrapper() { fpUninit(); } // won't be called if constructor throws
private:
UninitFuncType fpUninit; // avoid overhead of std::function not needed
};
template <typename InitFuncType, typename UninitFuncType, typename... Args>
RAIIWrapper<UninitFuncType>
raiiWrapper(InitFuncType fpInitFunc,
UninitFuncType fpUninitFunc,
Args&&... args)
{
return RAIIWrapper<typename std::decay<UninitFuncType>::type>
(std::move(fpInitFunc), std::move(fpUninitFunc), std::forward<Args>(args)...);
}
template <typename InitFuncType, typename SuccessValueType>
struct ReturnChecker {
InitFuncType func;
SuccessValueType success;
const char *errorString;
ReturnChecker(InitFuncType func,
SuccessValueType success,
const char *errorString)
: func(std::move(func)), success(std::move(success)), errorString(errorString) {}
template <typename... Args>
void operator()(Args&&... args)
{
if (func(std::forward<Args>(args)...) != success)
throw std::runtime_error(errorString);
}
};
template <typename InitFuncType, typename SuccessValueType,
typename Ret = ReturnChecker<InitFuncType, SuccessValueType> >
Ret checkReturn(InitFuncType func, SuccessValueType success, const char *errorString)
{
return Ret{func, success, errorString};
}
I also added functions to allow type deduction. Here is how to use it:
auto _ = raiiWrapper(checkReturn(glfwInit, GL_TRUE, "Failed to initialize GLFW"),
glfwTerminate);
Since having a function object that has a non-void return value causes the static_assert to fail, the following is impossible:
raiiWrapper(glfwInit, glfwTerminate); // fails compiling
If you really want to ignore it, you can add an ignoreReturn
function object. Also note that the return code checking can be as sophisticated as you want (like success has to be an even number) since you can write your own return code checker.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With