Я написал оболочку RAII для пар функций C, которые инициализируют и освобождают ресурсы, и в большинстве случаев она мне подходит.
#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");
}
Однако, скажем, когда функция возвращает void
лайк Enter/LeaveCriticalSection
Я не уверен, как идти и делать это в этом классе. Должен ли я специализировать класс для SuccessValueType = void
дело? Или что-то с параметром шаблона по умолчанию должно делать?
Я хотел бы отметить, что
Вам не нужна информация о вашей функции инициализации в вашем классе оболочки. Вам нужно только знать о функции деинициализации.
Вы можете создать вспомогательные функции для создания экземпляра вашей оболочки.
Я придумал следующее решение (мне понравилась идея обработки исключений @ipc)
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);
}
Пример:
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");
}
редактировать
RAII_wrapper_type
должен иметь семантику перемещения, и мы должны тщательно реализовать его конструктор перемещения, чтобы предотвратить uninit_f
от звонка несколько раз.
Я бы отделил логику возврата-проверки и RAII-упаковки
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};
}
Я также добавил функции, позволяющие выводить тип. Вот как это использовать:
auto _ = raiiWrapper(checkReturn(glfwInit, GL_TRUE, "Failed to initialize GLFW"),
glfwTerminate);
Поскольку наличие функционального объекта с возвращаемым значением, не равным void, приводит к сбою static_assert, следующее невозможно:
raiiWrapper(glfwInit, glfwTerminate); // fails compiling
Если вы действительно хотите игнорировать это, вы можете добавить ignoreReturn
функциональный объект. Также обратите внимание, что проверка кода возврата может быть настолько сложной, насколько вы хотите (например, успех должен быть четным числом), так как вы можете написать свою собственную проверку кода возврата.