Как улучшить систему обратного вызова функций в стиле c?

#c #templates #callback

#c #шаблоны #обратный вызов

Вопрос:

У меня есть класс, который я не могу изменить (СуперКласс), который имеет интерфейс для добавления обратного вызова, который мне не очень нравится, но я не могу сделать его лучше. Возможно ли избавиться от всех функций-оболочек, используя какую-нибудь магию шаблона?

 #include <iostream>
#include <string>
#include <vector>

enum EventType
{
    OkButtonPressed = 0,
    CancelButtonPressed,
    KeyPad1ButtonPressed,
    KeyPad2ButtonPressed,
    KeyPad3ButtonPressed,
    KeyPad4ButtonPressed,
    KeyPad5ButtonPressed,
    SomeOtherNonButtonEvent,
    EventType_count
};


// Not sure how the SuperClass is implemented, but from the outside all you can see is 
// the AddCallback(). The class can not be changed. Same applies to the callback typedef

typedef void (*EventCallback)(int argument, void *callbackOwner);
class SuperClass
{
private:
    #define CallbackPair std::pair<EventCallback, void*>
    std::vector<CallbackPair> m_callbackList[EventType_count];

public:
    void AddCallback(EventType eventType, EventCallback function, void * callbackOwner)
    {
        CallbackPair pair(function, callbackOwner);
        m_callbackList[eventType].push_back(pair);
    }

    void TriggerEvent(EventType type)
    {
        int someArguemt = (int)type;
        for(unsigned int i = 0; i < m_callbackList[type].size(); i  )
            m_callbackList[type][i].first(someArguemt, m_callbackList[type][i].second);
    }
};

class MyClass
{
public:
    void OnKeypadFunction(int argument){std::cout << "OnKeypadFunction called with " << argument << std::endl;}
    void OnOkFunction(int argument){std::cout << "OnOkFunction called with " << argument << std::endl;}
    void OnCancelFunction(int argument){std::cout << "OnCancelFunction called with " << argument << std::endl;}
};

// Wrapper functions which i would like to remove
static void s_OnKeypadFunctionWrapper(int arguemt, void *callbackOwner)
{
    ((MyClass*)callbackOwner)->OnKeypadFunction(arguemt);
}

static void s_OnOkFunctionWrapper(int arguemt, void *callbackOwner)
{
    ((MyClass*)callbackOwner)->OnOkFunction(arguemt);
}

static void s_OnCancelFunctionWrapper(int arguemt, void *callbackOwner)
{
    ((MyClass*)callbackOwner)->OnCancelFunction(arguemt);
}


int main()
{
    SuperClass super;
    MyClass myClass;

    // Register some callbacks
    super.AddCallback(KeyPad1ButtonPressed, s_OnKeypadFunctionWrapper, amp;myClass);
    super.AddCallback(KeyPad2ButtonPressed, s_OnKeypadFunctionWrapper, amp;myClass);
    super.AddCallback(KeyPad3ButtonPressed, s_OnKeypadFunctionWrapper, amp;myClass);
    super.AddCallback(KeyPad4ButtonPressed, s_OnKeypadFunctionWrapper, amp;myClass);
    super.AddCallback(KeyPad5ButtonPressed, s_OnKeypadFunctionWrapper, amp;myClass);
    super.AddCallback(OkButtonPressed, s_OnOkFunctionWrapper, amp;myClass);

    // How I would like to add the code..
    /*
    super.AddCallback(KeyPad1ButtonPressed, myClass->OnKeypadFunction, NULL);
    super.AddCallback(KeyPad2ButtonPressed, myClass->OnKeypadFunction, NULL);
    super.AddCallback(KeyPad3ButtonPressed, myClass->OnKeypadFunction, NULL);
    super.AddCallback(KeyPad4ButtonPressed, myClass->OnKeypadFunction, NULL);
    super.AddCallback(KeyPad5ButtonPressed, myClass->OnKeypadFunction, NULL);
    super.AddCallback(OkButtonPressed, myClass->s_OnOkFunctionWrapper, NULL);
    */


    // This is called some other place...
    super.TriggerEvent(OkButtonPressed);
    super.TriggerEvent(KeyPad1ButtonPressed);
    super.TriggerEvent(KeyPad5ButtonPressed);
}
  

Комментарии:

1. Честно говоря, я не думаю, что какое-либо количество «Магии шаблонов» было бы «лучше». Это только усложнило бы ситуацию. Что такого плохого / сложного в определении нескольких функций и передаче их другой функции?

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

3. Ну, независимо от этого, вам все равно нужно было бы написать n количество функций для фактической обработки события (событий), поэтому я не уверен, что вы получаете, используя шаблоны. Не могли бы вы просто написать одну функцию и включить тип события?

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

5. Хорошо, но код не обязательно должен быть «крутым», он должен быть правильным и как можно более простым. Усложнение вашего кода только для того, чтобы сделать его «крутым», не принесет вам очков в глазах ваших коллег в реальном мире.

Ответ №1:

Как насчет чего-то подобного:

 template <typename T, void (T::*MemberFunction)(int)>
struct CallbackHelper
{
    static void EventHandler(int argument, void *callbackOwner)
    {
        auto myClass = static_cast<T*>(callbackOwner);
        ((myClass)->*(MemberFunction))(argument);
    }
};
  

Что позволило бы вам зарегистрировать ваши обработчики следующим образом:

 super.AddCallback(OkButtonPressed, CallbackHelper<MyClass, amp;MyClass::OnOkFunction>::EventHandler, amp;myClass);
  

Комментарии:

1. Идеально! Похоже, это именно то, что я искал

2. Возможно ли обернуть шаблоны в макрос?

3. MacroCall (MyClass, amp;MyClass::OnOkFunction) или даже лучше MacroCall (MyClass, OnOkFunction)

4. Да, конечно, макрос просто выполняет замену. Вы могли бы написать что-то вроде этого: «#define REGISTER(s, p, f,e) s.addCallback(e, CallbackHelper<MyClass, amp;MyClass::f>::EventHandler, p)», который вызывался бы следующим образом: «REGISTER(super, amp;MyClass, OnOkFunction, OkButtonPressed);». Это может сделать код немного менее читаемым, но это может зависеть только от личных предпочтений.

5. Верно, это делает его очень нечитаемым. На самом деле я думал больше в духе super. addCallback (OkButtonPressed, MacroCall (MyClass, OnOkFunction), amp; MyClass), но я вижу из вашего answare, что фактические аргументы шаблона не использовались в макросе, поэтому я предполагаю, что это невозможно сделать?