Подтвердить что ты не робот

Шаблон магии для обертывания C обратных вызовов, которые принимают параметры void *?

Скажем, я использую C API, который позволяет вам регистрировать обратные вызовы, которые принимают закрытие void*:

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

В С++ хорошо иметь более сильные типы, чем void*, поэтому я хочу создать оболочку, которая позволяет мне регистрировать строго типизированные обратные вызовы С++:

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() работать выше, сохраняя при этом характеристику производительности с нулевой нагрузкой? Я хочу что-то, что будет работать на С++ 98.

4b9b3361

Ответ 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

Эта функция шаблона незначительно улучшает синтаксис.

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 и С++ ABI.

Ответ 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);
}

Используя объект, вы можете ввести реальное состояние.
У вас есть чистый С++-интерфейс с правильными типами объектов.
Его простой в использовании вы просто извлекаете из CB и реализуете action().

Это также имеет то же количество фактических вызовов функций, которые вы используете. Потому что в вашем примере вы передаете указатель функции на оболочку (который не может быть встроен (он может, но это потребует больше статического анализа, чем текущие компиляторы)). По-видимому, он встроен.