简体   繁体   English

当使用仿函数时,我怎样才能在C ++中编写带有自定义函数调用的模板化RAII包装器?

[英]How can I, in C++, write a templated RAII wraper with custom function calls when functors are not an option?

I am currently working on an RAII system for OpenGl in standard¹ C++ 17 while making very heavy use of templates. 我目前正在使用标准的C ++ 17中的OpenGl RAII系统,同时大量使用模板。 Right now, the part of the system that I am working on is to bind and unbind various OpenGl objects via a common template with later using declarations to create easy aliases for each type. 现在,我正在处理的系统部分是通过公共模板绑定和解除各种OpenGl对象的绑定,稍后使用声明为每种类型创建简单的别名。 The following is a relevant excerpt of my header file that demonstrates the general technique: 以下是我的头文件的相关摘录,演示了一般技术:

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>;

When I first attempted this class, I used non-void pointers in the template<> declaration (eg template<typename ... Args, void (*)(Args...)> ), but this caused problems because 1) it is more difficult to manually specify Args , and 2) CLion told me that the variadic argument must be last. 当我第一次尝试这个类时,我在template<>声明中使用了非void指针(例如template<typename ... Args, void (*)(Args...)> ),但这会导致问题,因为1)它更难以手动指定Args ,2)CLion告诉我,可变参数必须是最后的。

I then moved the variadic argument to be last, solving both problems. 然后我将可变参数变为最后一个,解决了这两个问题。 This prevented the function argument from accessing the argument pack to unpack, however. 但是,这会阻止函数参数访问参数包以解压缩。

To solve this issue, I made the template arguments pointers to void and then casted them to more useful types in the class body, where both the function's address and the parameter pack are available. 为了解决这个问题,我将模板参数指针设置为void,然后将它们转换为类体中更有用的类型,其中函数的地址和参数包都可用。 Due to my understanding of function pointers as no different than regular pointers, except where their address is to the machine code of the function in question, I figured that there would be no problem other than mildly compromised type safety² with this method. 由于我对函数指针的理解与常规指针没有什么不同,除非它们的地址是相关函数的机器代码,我认为除了使用这种方法的轻度类型安全²之外没有问题。

Unfortunately, that proved to be false when my compiler wouldn't let me cast the function pointer to void* , explicitly or otherwise. 不幸的是,当我的编译器不允许我将函数指针显式地转换为void*时,这被证明是错误的。 After some research, I concluded that my previously apparent solution of pointers to void wasn't much of a solution because it is actually undefined behavior to cast a function pointer to void* in C++. 经过一些研究,我得出结论,我之前明显的void指针解决方案并不是解决方案,因为在C ++中将函数指针强制转换为void*实际上是未定义的行为。

I can't use a functor because I want the users of my class to be able to get along without even knowing that it is a template class via my using declarations, but a functor would require each instantiation of it to pass an instance of the functor type. 我不能使用仿函数,因为我希望我的类的用户能够在不知道它是通过我的使用声明的模板类的情况下相处,但是仿函数需要每个实例化它来传递一个实例仿函类型。

As such, I ask the very smart people of stack overflow: How can I resolve this issue? 因此,我向非常聪明的人询问堆栈溢出:我该如何解决这个问题?

1: This means that I am strongly against any undefined behavior that will Probably Work™ because portability is important to me. 1:这意味着我强烈反对任何可能工作的未定义行为,因为可移植性对我很重要。 2: Although the casts themselves are unsafe, the compiler should stop compilation with an error in case of any issues when the template, and thus the cast, is instantiated. 2:虽然强制转换本身是不安全的,但是在模板和实例化实例化时出现任何问题时,编译器应该停止编译并出现错误。 As I intended for users of my code to use it solely via the using declarations, such a possibly cryptic error is a very minor problem. 正如我打算让我的代码的用户仅通过使用声明来使用它,这样一个可能是神秘的错误是一个非常小的问题。

Since you are using C++17, you can just use auto template parameters. 由于您使用的是C ++ 17,因此您只需使用auto模板参数即可。 You can add static assertions in the class body to ensure that the arguments are actually function pointers. 您可以在类体中添加静态断言,以确保参数实际上是函数指针。

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>;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM