12

假设我正在使用一个 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 中工作的东西。

4

3 回答 3

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 不同的系统的风险。

于 2013-05-17T08:13:41.613 回答
1

我发现这个问题的答案比这里给我的其他答案更好!(实际上是谷歌内部的另一位工程师提出的)。

您必须重复两次函数名称,但这可以通过宏来解决。

基本模式是:

// 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* 参数调用此函数。
  • 没有虚函数开销或间接性。
于 2014-09-03T23:19:19.973 回答
-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 派生并实现 action()。

这也具有与您使用的相同数量的实际函数调用。因为在您的示例中,您将函数指针传递给包装器(它不能被内联(它可以但它需要比当前编译器更多的静态分析))。 显然它是内联的。

于 2013-05-17T07:03:08.313 回答