用于包装带有void *参数的C回调的模板魔术?

时间:2013-05-17 06:14:56

标签: c++ c callback c++98

假设我使用的是一个C API,可以让你注册一个void*关闭的回调:

void register_callback(void (*func)(void*), void *closure);

在C ++中,拥有比void*更强的类型是很好的,所以我想创建一个包装器,让我注册强类型的C ++回调:

template <typename T, void F(T*)>
void CallbackWrapper(void *p) {
  return F(static_cast<T*>(p));
}

void MyCallback(int* param) {}

void f(void *closure) {
  register_callback(CallbackWrapper<int, MyCallback>, closure);
}

这没关系。这个解决方案的一个很好的属性是它可以将我的回调内联到包装器中,所以这个包装方案没有开销。我认为这是一个要求。

但如果我能让API看起来更像这样,那就太好了:

void f2() {
  RegisterCallback(MyCallback, closure);
}

我希望通过推断模板参数来实现上述目的。但我无法弄清楚如何让它发挥作用。到目前为止我的尝试是:

template <typename T>
void RegisterCallback(void (*f)(T*), T* closure) {
  register_callback(CallbackWrapper<T, f>, closure);
}

但这不起作用。任何人都有一个神奇的咒语,会使f2()在上面工作,同时保持零开销性能特征?我想要一些适用于C ++ 98的东西。

3 个答案:

答案 0 :(得分:4)

此模板功能可略微改善语法。

template <typename T, void F(T*)>
void RegisterCallback (T *x) {
    register_callback(CallbackWrapper<T, F>, x);
}

int x = 4;
RegisterCallback<int, MyCallback>(&x);

如果您愿意使用函子而不是函数来定义回调,那么您可以简化一些事情:

#ifdef HAS_EXCEPTIONS
# define BEGIN_TRY try {
# define END_TRY } catch (...) {}
#else
# define BEGIN_TRY
# define END_TRY
#endif

template <typename CB>
void CallbackWrapper(void *p) {
    BEGIN_TRY
    return (*static_cast<CB*>(p))();
    END_TRY
}

struct MyCallback {
    MyCallback () {}
    void operator () () {}
};

template <typename CB>
void RegisterCallback (CB &x) {
    register_callback(CallbackWrapper<CB>, &x);
}

MyCallback cb;
RegisterCallback(cb);

但是,正如其他人所提到的那样,您冒着将代码无法正确移植到C ABI和C ++ ABI不同的系统的风险。

答案 1 :(得分:1)

我发现这个问题比这里给出的其他答案更好的答案! (实际上是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 <class R, class P1, R F(P1)>
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 <class R, class P1>
struct FuncSig1 {
  template <R F(P1)>
  Func1<R, P1, F> GetFunc() { return Func1<R, P1, F>(); }
};

// ...

// Overloaded template function that can construct the appropriate FuncSig*
// class given a function pointer by deducing the template parameters.
template <class R, class P1>
inline FuncSig1<R, P1> MatchFunc(R (*f)(P1)) {
  (void)f;  // Only used for template parameter deduction.
  return FuncSig1<R, P1>();
}

// ...

// Function that casts the first parameter to the given type.
template <class R, class P1, R F(P1)>
R CastArgument(void *c) {
  return F(static_cast<P1>(c));
}

template <class F>
struct WrappedFunc;

template <class R, class P1, R F(P1)>
struct WrappedFunc<Func1<R, P1, F> > {
  typedef Func1<R, void*, CastArgument<R, P1, F> > Func;
};

template <class T>
generic_func_t *GetWrappedFuncPtr(T func) {
  typedef typename WrappedFunc<T>::Func Func;
  return Func().Call;
}

// User code:

#include <iostream>

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<StronglyTypedFunc>());
  int x = 5;
  f(&x);
}

这不简短或简单,但它是正确的,有原则的,符合标准的!

它让我得到了我想要的东西:

  • 用户可以使用指向特定事物的指针编写StronglyTypedFunc()。
  • 可以使用void *参数调用此函数。
  • 没有虚函数开销或间接。

答案 2 :(得分:-3)

为什么不让你的闭包真正闭合(通过包含真正的类型状态)。

class CB
{
    public:
        virtual ~CB() {}
        virtual void action() = 0;
};

extern "C" void CInterface(void* data)
{
    try
    {
        reinterpret_cast<CB*>(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派生并实现动作()。

这与您使用的实际函数调用次数相同。因为在你的例子中你传递了一个函数指针给包装器(它不能内联(它可以,但它需要更多的静态分析,然后当前的编译器这样做)。) 显然它是内联的。