Как хранить и вызывать функции по thiscall?

Я делаю систему регистрации колбэков от аппаратных устройств и перевожу их на c++. Есть библиотечная функция со слабым связыванием в c-файле, которая вызывается при наступлении какого-то аппаратного события. Я её в своём файле переопределяю, и перенаправляю вызов в метод некоторого класса.

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

Варианты с использованием std::function и подобных типов не подходят, так как они выделяют память в куче. Вот что у меня получилось:

template<class Callable, class Object>
struct Callback {
    
    Callback(Callable f, Object o) :
        func(f),
        object(o)
        {}
    
    Callable func;
    Object object;
};

class CallbackRegistry {
public:
    template<class Callable, class Object>
    static void setCallback(Object object, Callable func)
    {
        Callback1<Callable, Object> = Callback<Callable, Object>(func, object);
    }
    
    //Здесь надо хранить объект this и функцию. Возможно, надо уйти от шаблонности.
    template<class Callable, class Object>
    static Callback<Callable, Object> Callback1; 
};


template<class Callable, class Object>
Callback<Callable, Object> CallbackRegistry::Callback1 = Callback<Callable, Object>(nullptr, nullptr);

//Эта функция вызывается аппаратно. В библиотечном файле она объявлена со слабым связыванием.
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    //Как определить эти типы? Если уходить от шаблонности переменной Callback1, то их здесь не будет
    std::invoke(CallbackRegistry::Callback1<?, ?>.func, 
                CallbackRegistry::Callback1<?, ?>.object /* this */);
}

CallbackRegistry::setCallback(someObject, comeMethodOfObject);

Проблема в том, что для реализации нужного поведения надо как-то избавиться от шаблонности в переменной Callback1. Где-то отдельно хранить тип? Но как - я пока не понимаю.


Ответы (1 шт):

Автор решения: Chorkov

Храните в callback-е данные конкретного типа, а не шаблонные. Можно выделить специальный интерфейс для этого.

struct ICallable
{
    virtual void operator() (void) const =0;
};

// std::function<void(void)> - своими руками, без выделения памятию.
template<class Lambda>
struct Callable : ICallable {
    Lambda lambda;
    Callable(Lambda lambda) : lambda(lambda) {}
    void operator() (void) const { lambda(); }
};

class CallbackRegistry {
public:
    // Сделаем универсально, для любого invorkable набора аргументов.
    template<class... Args>
    static void setCallback(Args...args)
    {
        auto binded=[=](){ std::invoke(args...); }; // сворачиваем набор аргументов в один.
        static Callable<decltype(binded)> instance( binded ); // создаем экземпляр Callable. 
        assert(callback==nullptr);
        callback=&instance;
    }
    static ICallable* callback; 
};
ICallable* CallbackRegistry::callback = nullptr;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if(CallbackRegistry::callback!=nullptr)
        (*CallbackRegistry::callback)();
}   


struct Test
{
    void foo() {}
} test;
CallbackRegistry::setCallback(  &Test::foo, & test );
→ Ссылка