当函子不是一个选项时,我如何在 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>;