用于函数对和模板专业化的 RAII 包装器
Posted
技术标签:
【中文标题】用于函数对和模板专业化的 RAII 包装器【英文标题】:RAII wrapper for function pairs and template specialization 【发布时间】:2013-01-03 08:18:44 【问题描述】:我为初始化和释放资源的 C 函数对编写了一个 RAII 包装器,它在大多数情况下都能很好地为我服务。
#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
案例的课程吗?或者使用默认模板参数的东西应该做什么?
【问题讨论】:
你需要SuccessValueType
和successValue
作为类模板参数吗?它们不能是构造函数的参数吗?那么你可以创建两个单独的构造函数......只是大声思考
哇,我没想到,现在让我试试 :)
@AndyProwl:哦,等等,但这会让我的调用者网站看起来很丑:(而且当成功值在@编译时已知时,传递它@运行时是不必要的。
那只是又一个论点,不是吗? GL_TRUE
应该足够了,应该推导出它的类型,这样你就不必指定它了
欣喜若狂:) 正确的复制总是与 RAII 相关
【参考方案1】:
我想指出,
您不需要有关包装类中的初始化函数的信息。您只需要了解未初始化函数即可。
您可以创建函数助手来实例化您的包装器。
我想出了以下解决方案(我喜欢@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
多次调用。
【讨论】:
这很好很优雅:) 我看到一个简单的函数解决了这里的问题。 你能让这个不可复制吗,当我通常删除复制 ctor 和赋值运算符函数时,编译器抱怨从捕获返回 RAII_wrapper_type,这也是一个副本。 @legends2k,我添加了移动问题灵魂。_empty
不是多余的吗?你不能只检查_f != nullptr
吗?
@legends2k,如果你的UninitF
实际上是C
函数指针,那没问题,但如果它是一个仿函数,你就不能同时分配nullptr
给它,也不将函子与nullptr
进行比较【参考方案2】:
我会将返回检查和 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 Retfunc, success, errorString;
我还添加了允许类型推断的函数。使用方法如下:
auto _ = raiiWrapper(checkReturn(glfwInit, GL_TRUE, "Failed to initialize GLFW"),
glfwTerminate);
由于函数对象具有非 void 返回值会导致 static_assert 失败,以下是不可能的:
raiiWrapper(glfwInit, glfwTerminate); // fails compiling
如果你真的想忽略它,你可以添加一个ignoreReturn
函数对象。另请注意,返回码检查可以根据需要进行复杂的操作(例如成功必须是偶数),因为您可以编写自己的返回码检查器。
【讨论】:
+1 向我展示了function<>
不是必需的,也是一个好的答案,我喜欢这个解决方案,但让我们给它更多时间,因为我仍然觉得这可以在不解耦的情况下完成2 个操作。
您确定不需要该功能吗?我尝试编译,clang 抛出 错误:数据成员实例化为函数类型'void ()'。我认为您打算将其用作函数指针,即 UninitFuncType*。我不想这样,因此我使用了function
。
做一些std::decay
来解决这个问题。
@ipc: 函子 ReturnChecker 的 operator() 应该接受可变参数;为此编辑了答案。
这很容易解决,但我更喜欢@Lol4t0 的解决方案,所以使用它们 ;)以上是关于用于函数对和模板专业化的 RAII 包装器的主要内容,如果未能解决你的问题,请参考以下文章