当函子不是一个选项时,我如何在 C++ 中编写带有自定义函数调用的模板化 RAII 包装器?
How can I, in C++, write a templated RAII wraper with custom function calls when functors are not an option?
我目前正在使用标准¹ C++ 17 为 OpenGl 开发 RAII 系统,同时大量使用模板。现在,我正在处理的系统部分是通过一个通用模板绑定和取消绑定各种 OpenGl 对象,然后使用声明为每种类型创建简单的别名。以下是我的头文件的相关摘录,演示了一般技术:
template<typename T, void *bind, void *unbind, typename ... Args>
class RaiiGlBinding{
public:
explicit RaiiGlBinding(const T &t, Args... args) : m_unbindArgs(std::make_tuple(t, args...)) { bind(t, args...); }
~RaiiGlBinding() { if(m_isDestructable) std::apply(unbind_typed, m_unbindArgs); }
private:
static constexpr auto bind_Vaotyped = static_cast<void (*)(T, Args...)>(bind);
static constexpr auto unbind_typed = static_cast<void (*)(T, Args...)>(unbind);
bool m_isDestructable = true;
std::tuple<T, Args...> m_unbindArgs;
};
Vao
namespace glraiidetail{
inline void bindBuffer(GLuint buffer, GLenum target) { glBindBuffer(target, buffer); }
inline void unbindBUffer(GLuint buffer, GLenum target) { glBindBuffer(target, 0); }
}
using RaiiBufferBinding = RaiiGlBinding<GLuint, &glraiidetail::bindBuffer, &glraiidetail::unbindBuffer>;
当我第一次尝试这个 class 时,我在 template<>
声明中使用了非空指针(例如 template<typename ... Args, void (*)(Args...)>
),但这导致了问题,因为 1) 它更难手动指定 Args
,以及 2) CLion 告诉我可变参数必须放在最后。
然后我将可变参数移到最后,解决了这两个问题。但是,这阻止了函数参数访问参数包以解包。
为了解决这个问题,我将模板参数指针设为 void,然后将它们转换为 class 主体中更有用的类型,其中函数的地址和参数包都可用。由于我对函数指针的理解与常规指针没有什么不同,除了它们的地址指向所讨论函数的机器代码之外,我认为使用这种方法除了稍微损害类型安全之外不会有任何问题²。
不幸的是,当我的编译器不允许我将函数指针显式或以其他方式转换为 void*
时,这被证明是错误的。经过一些研究,我得出结论,我之前明显的 void 指针解决方案并不是一个很好的解决方案,因为在 C++ 中将函数指针转换为 void*
实际上是未定义的行为。
我不能使用仿函数,因为我希望我的 class 的用户能够通过我的 using 声明在不知道它是模板 class 的情况下相处,但是函子将要求它的每个实例化都传递一个函子类型的实例。
所以请教stack overflow的高手们:请问如何解决这个问题?
1:这意味着我强烈反对任何可能会起作用的未定义行为™,因为可移植性对我来说很重要。
2:虽然强制转换本身是不安全的,但如果在实例化模板和强制转换时出现任何问题,编译器应该停止编译并报错。因为我打算让我的代码的用户仅通过 using 声明来使用它,所以这种可能神秘的错误是一个非常小的问题。
由于您使用的是 C++17,因此您可以只使用 auto
模板参数。您可以在 class 主体中添加静态断言,以确保参数实际上是函数指针。
template <typename T, auto bind, auto unbind, typename... Args>
class RaiiGlBinding {
public:
explicit RaiiGlBinding(const T &t, Args... args)
: m_unbindArgs(std::make_tuple(t, args...)) {
bind(t, args...);
}
~RaiiGlBinding() {
if (m_isDestructable)
std::apply(unbind, m_unbindArgs);
}
private:
bool m_isDestructable = true;
std::tuple<T, Args...> m_unbindArgs;
};
namespace glraiidetail {
inline void bindBuffer(GLuint buffer, GLenum target) {
glBindBuffer(target, buffer);
}
inline void unbindBuffer(GLuint buffer, GLenum target) {
glBindBuffer(target, 0);
}
} // namespace glraiidetail
using RaiiBufferBinding = RaiiGlBinding<GLuint, &glraiidetail::bindBuffer,
&glraiidetail::unbindBuffer>;
我目前正在使用标准¹ C++ 17 为 OpenGl 开发 RAII 系统,同时大量使用模板。现在,我正在处理的系统部分是通过一个通用模板绑定和取消绑定各种 OpenGl 对象,然后使用声明为每种类型创建简单的别名。以下是我的头文件的相关摘录,演示了一般技术:
template<typename T, void *bind, void *unbind, typename ... Args>
class RaiiGlBinding{
public:
explicit RaiiGlBinding(const T &t, Args... args) : m_unbindArgs(std::make_tuple(t, args...)) { bind(t, args...); }
~RaiiGlBinding() { if(m_isDestructable) std::apply(unbind_typed, m_unbindArgs); }
private:
static constexpr auto bind_Vaotyped = static_cast<void (*)(T, Args...)>(bind);
static constexpr auto unbind_typed = static_cast<void (*)(T, Args...)>(unbind);
bool m_isDestructable = true;
std::tuple<T, Args...> m_unbindArgs;
};
Vao
namespace glraiidetail{
inline void bindBuffer(GLuint buffer, GLenum target) { glBindBuffer(target, buffer); }
inline void unbindBUffer(GLuint buffer, GLenum target) { glBindBuffer(target, 0); }
}
using RaiiBufferBinding = RaiiGlBinding<GLuint, &glraiidetail::bindBuffer, &glraiidetail::unbindBuffer>;
当我第一次尝试这个 class 时,我在 template<>
声明中使用了非空指针(例如 template<typename ... Args, void (*)(Args...)>
),但这导致了问题,因为 1) 它更难手动指定 Args
,以及 2) CLion 告诉我可变参数必须放在最后。
然后我将可变参数移到最后,解决了这两个问题。但是,这阻止了函数参数访问参数包以解包。
为了解决这个问题,我将模板参数指针设为 void,然后将它们转换为 class 主体中更有用的类型,其中函数的地址和参数包都可用。由于我对函数指针的理解与常规指针没有什么不同,除了它们的地址指向所讨论函数的机器代码之外,我认为使用这种方法除了稍微损害类型安全之外不会有任何问题²。
不幸的是,当我的编译器不允许我将函数指针显式或以其他方式转换为 void*
时,这被证明是错误的。经过一些研究,我得出结论,我之前明显的 void 指针解决方案并不是一个很好的解决方案,因为在 C++ 中将函数指针转换为 void*
实际上是未定义的行为。
我不能使用仿函数,因为我希望我的 class 的用户能够通过我的 using 声明在不知道它是模板 class 的情况下相处,但是函子将要求它的每个实例化都传递一个函子类型的实例。
所以请教stack overflow的高手们:请问如何解决这个问题?
1:这意味着我强烈反对任何可能会起作用的未定义行为™,因为可移植性对我来说很重要。 2:虽然强制转换本身是不安全的,但如果在实例化模板和强制转换时出现任何问题,编译器应该停止编译并报错。因为我打算让我的代码的用户仅通过 using 声明来使用它,所以这种可能神秘的错误是一个非常小的问题。
由于您使用的是 C++17,因此您可以只使用 auto
模板参数。您可以在 class 主体中添加静态断言,以确保参数实际上是函数指针。
template <typename T, auto bind, auto unbind, typename... Args>
class RaiiGlBinding {
public:
explicit RaiiGlBinding(const T &t, Args... args)
: m_unbindArgs(std::make_tuple(t, args...)) {
bind(t, args...);
}
~RaiiGlBinding() {
if (m_isDestructable)
std::apply(unbind, m_unbindArgs);
}
private:
bool m_isDestructable = true;
std::tuple<T, Args...> m_unbindArgs;
};
namespace glraiidetail {
inline void bindBuffer(GLuint buffer, GLenum target) {
glBindBuffer(target, buffer);
}
inline void unbindBuffer(GLuint buffer, GLenum target) {
glBindBuffer(target, 0);
}
} // namespace glraiidetail
using RaiiBufferBinding = RaiiGlBinding<GLuint, &glraiidetail::bindBuffer,
&glraiidetail::unbindBuffer>;