用于包装带有void *参数的C回调的模板魔术?
假设我正在使用一个C API,它允许您注册带有void*
闭包的回调:
void register_callback(void (*func)(void*), void *closure);
在C ++中,拥有比void*
更强的类型是很好的,所以我想创建一个包装器,让我注册强类型的C ++回调:
template void CallbackWrapper(void *p) { return F(static_cast(p)); } void MyCallback(int* param) {} void f(void *closure) { register_callback(CallbackWrapper, closure); }
这没关系。 这个解决方案的一个很好的属性是它可以将我的回调内联到包装器中,所以这个包装方案没有开销。 我认为这是一个要求。
但是如果我能让API看起来更像这样会很好:
void f2() { RegisterCallback(MyCallback, closure); }
我希望我可以通过推断模板参数来实现上述目的。 但我无法弄清楚如何使其发挥作用。 我到目前为止的尝试是:
template void RegisterCallback(void (*f)(T*), T* closure) { register_callback(CallbackWrapper, closure); }
但这不起作用。 任何人都有一个神奇的咒语,使f2()
工作在上面,同时保持零开销性能特征? 我想要一些适用于C ++ 98的东西。
此模板函数略微改进了语法。
template void RegisterCallback (T *x) { register_callback(CallbackWrapper, x); } int x = 4; RegisterCallback(&x);
如果您愿意使用函子而不是函数来定义回调,那么您可以更简化一些事情:
#ifdef HAS_EXCEPTIONS # define BEGIN_TRY try { # define END_TRY } catch (...) {} #else # define BEGIN_TRY # define END_TRY #endif template void CallbackWrapper(void *p) { BEGIN_TRY return (*static_cast(p))(); END_TRY } struct MyCallback { MyCallback () {} void operator () () {} }; template void RegisterCallback (CB &x) { register_callback(CallbackWrapper, &x); } MyCallback cb; RegisterCallback(cb);
但是,正如其他人所提到的,您冒着将代码无法正确移植到C ABI和C ++ ABI不同的系统的风险。
我发现这个问题比这里给出的其他答案更好的答案! (实际上是Google内部的另一位工程师提出了这个建议)。
您必须重复两次函数名称,但可以使用宏来解决。
基本模式是:
// Func1, Func2, Func3: Template classes representing a function and its // signature. // // Since the function is a template parameter, calling the function can be // inlined at compile-time and does not require a function pointer at runtime. // These functions are not bound to a handler data so have no data or cleanup // handler. template struct Func1 { typedef R Return; static R Call(P1 p1) { return F(p1); } }; // ... // FuncSig1, FuncSig2, FuncSig3: template classes reflecting a function // *signature*, but without a specific function attached. // // These classes contain member functions that can be invoked with a // specific function to return a Func/BoundFunc class. template struct FuncSig1 { template Func1 GetFunc() { return Func1(); } }; // ... // Overloaded template function that can construct the appropriate FuncSig* // class given a function pointer by deducing the template parameters. template inline FuncSig1 MatchFunc(R (*f)(P1)) { (void)f; // Only used for template parameter deduction. return FuncSig1(); } // ... // Function that casts the first parameter to the given type. template R CastArgument(void *c) { return F(static_cast(c)); } template struct WrappedFunc; template struct WrappedFunc > { typedef Func1 > Func; }; template generic_func_t *GetWrappedFuncPtr(T func) { typedef typename WrappedFunc::Func Func; return Func().Call; } // User code: #include typedef void (generic_func_t)(void*); void StronglyTypedFunc(int *x) { std::cout << "value: " << *x << "\n"; } int main() { generic_func_t *f = GetWrappedFuncPtr( MatchFunc(StronglyTypedFunc).GetFunc()); int x = 5; f(&x); }
这不简短或简单,但它是正确的,有原则的,符合标准的!
它让我得到了我想要的东西:
- 用户可以编写StronglyTypedFunc()来获取指向特定事物的指针。
- 可以使用void *参数调用此函数。
- 没有虚函数开销或间接。
为什么不让你的闭包真正闭合(通过包括真正的类型状态)。
class CB { public: virtual ~CB() {} virtual void action() = 0; }; extern "C" void CInterface(void* data) { try { reinterpret_cast(data)->action(); } catch(...){} // No gurantees about throwing exceptions across a C ABI. // So you need to catch all exceptions and drop them // Or probably log them } void RegisterAction(CB& action) { register_callback(CInterface, &action); }
通过使用对象,您可以引入真实状态。
你有一个干净的C ++接口与正确的类型对象。
它易于使用,您只需从CB派生并实现action()。
这也与您使用的实际函数调用数相同。 因为在你的例子中你传递了一个函数指针给包装器(它不能内联(它可以,但它需要更多的静态分析,然后当前的编译器这样做))。 显然它确实是内联的。