C++: функции-члены класса как обратные вызовы событий

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

Логичным способом сделать это является использование указателей на функции. Было бы легко передать указатель на нужную функцию обратного вызова диспетчеру событий для регистрации. Функция обратного вызова события всегда будет возвращать int и принимать void* в качестве аргумента.

Однако я не хочу регистрировать статические глобальные функции в качестве обратных вызовов событий — я хотел бы сделать это с помощью функций-членов класса.

  • Возможно ли это сделать с помощью C++? Хранение и вызов указателей на функции-члены разных классов, но с одним и тем же заголовком функции.

  • Если это невозможно, есть ли у вас какие-либо предложения о том, как я могу обойти это? Мне бы очень хотелось добавить прослушиватели событий непосредственно в мои классы.


person Jarx    schedule 03.03.2011    source источник
comment
Приложите некоторые усилия, чтобы избежать void*. Обычно это не требуется в C++, и это открывает возможности для странных ошибок в более сложных системах: это обеспечивает согласованность между отправителем и получателем без помощи компилятора.   -  person stefaanv    schedule 03.03.2011


Ответы (7)


Да, это возможно. C++0x имеет класс function, который обрабатывает это, а также другие указали, что Boost имеет аналогичные возможности.

Вы также можете свернуть свой собственный, но синтаксис не для слабонервных:

#include <iostream>

class Callable
{
    public:

        virtual ~Callable() {}
        virtual int operator() (void* args) = 0;
};

class CallableFreeFunction  : public Callable
{
    public:

        CallableFreeFunction(int (*func)(void*)) : func_(func) {}

        virtual int operator() (void* args) { return (*func_)(args); }

    private:

        int (*func_)(void*);
};

template <typename tClass>
class ClassMemberCallable : public Callable
{
    public:

        ClassMemberCallable(tClass* instance, int (tClass::*memberfunction)(void*)) : instance_(instance), memberfunc_(memberfunction) {}

        virtual int operator() (void* args) { return (instance_->*memberfunc_)(args); }

    private:

        tClass* instance_;
        int (tClass::*memberfunc_)(void*);
};

class Foo
{
    public:

        int derp(void* args)
        {
            std::cout << args << '\n';
            return 2;
        }
};

int freefunctionfoo(void* args)
{
    std::cout << "free" << args << '\n';
    return 2;
}

int main(int argc, char* argv[])
{
    Foo myfoo;

    Callable* callable = new ClassMemberCallable<Foo>(&myfoo, &Foo::derp);

    (*callable)(0);

    delete callable;

    callable = new CallableFreeFunction(freefunctionfoo);

    (*callable)(0);

    delete callable;

    std::cin.get();

    return 0;
}

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

http://www.newty.de/fpt/index.html

http://www.parashift.com/c++-faq-lite/pointers-to-members.html

Я бы также рекомендовал посмотреть на это для большего количества идей:

http://www.codeproject.com/KB/cpp/FastDelegate.aspx

person luke    schedule 03.03.2011

Конечно возможно! Взгляните на Boost.Signal2 и Boost.Bind.

Boost.Signal2 в основном реализует систему сигналов и слотов, которая именно то, что вам нужно. Затем вы можете использовать boost::bind, который является обобщением std::bind1st и std::bind2nd, чтобы получить оболочки объектов функций практически для всего, что вы можете придумать (в вашем случае методы-члены). Это действительно мощно.

См. это официальное руководство по ускорению.

person fouronnes    schedule 03.03.2011

Вот моя не очень удачная попытка выполнить подобную работу: прежде всего вам нужен базовый класс обработчика событий, давайте пока назовем его EvtHandler:

class Event; //implement this yourself, it shall contain general but good info about event

class EvtHandler
{
public:
    virtual void handleEvent (Event & evt);
};

Затем каждый класс, который должен каким-то образом обрабатывать события, должен быть производным от этого класса, и они могут реализовывать новые функции столько раз, сколько захотят, если они возвращают один и тот же тип данных ( void в данном случае) и получить те же параметры (в данном случае Event). Нравится:

class Foo : public EvtHandler
{
public:
    void handleFooEvent (Event & event);
};

Затем я реализовал центры сообщений для каждого специального события, которые должны были регистрировать слушателей и отправлять события при необходимости:

class ShutdownMessageCenter
{
typedef std::map<EventHandler *, event_func> ListenerMap; 
public:

    void register (EvtHandler * handler, void(EvtHandler::*memFunc)(Event &)) {
        m_lmap[handler] = memFunc;
    }

     void callListeners () {
         Event shutdown_event (EM_SHUTDOWN /*just imagine this can mean something, idk*/);
         ListenerMap::iterator itr = m_lmap.begin ();
         for (; itr != m_lmap.end(); ++itr) {
            EvtHandler * handler = itr->first;
            void (EvtHandler::*func)(Event &) = itr->second;
            (handler->*func)(shutdown_event);
          }
      }

private:
   ListenerMap m_lmap;
};

Затем вы можете зарегистрировать свои EvtHandler, например, в этом конкретном центре сообщений!

ShutdownMessageCenter message_center;
EvtHandler * some_handler = new EvtHandler ();
Foo * some_foo = new Foo ();
message_center.register (some_handler, &EvtHandler::handleEvent);
message_center.register (some_foo, static_cast<void (EvtHandler::*)(Event &)>(&Foo::handleFooEvent);
message_center.callListeners ();

Но опять же, это совсем не хорошо, просто решил поделиться! Извините за беспорядок, ха-ха!

person Parsa Jamshidi    schedule 03.03.2011

Я не совсем уверен, что вы хотите заархивировать, но, возможно, вам следует взглянуть на Увеличить сигналы2

Это весьма полезно, если вы хотите создать какой-то механизм Сигнал/Слот.

person mkaes    schedule 03.03.2011

Нет, это невозможно (если вы не используете c++/cli с .net).

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

person J.N.    schedule 03.03.2011

Самое близкое, что мне удалось, это зарегистрировать статическую функцию-член в качестве обратного вызова. Статический член принимает указатель объекта (this) в качестве аргумента в дополнение к аргументам, отправленным обработчиком событий, и использует его для вызова функции-члена.

class myClass{
public:
  static void callback(void *arg, void *obj)
  {
    if (obj)
      reinterpret_cast<myClass*>(obj)->cb(arg);
  }

private:
  void cb(void *arg);
};

Зарегистрируйте myClass::callback и this у своего обработчика. Возможно, вам придется обернуть это в структуру, на которую ссылается arg, если вы ограничены в том, что может быть возвращено.

person DanS    schedule 03.03.2011

Я использую lukes answer с SWIG, потому что SWIG не поддерживает все функции C ++ 11 ... Это, вероятно, можно улучшить еще больше с подходом Parsa Jamshidis.

Я изменил его, чтобы охватить еще больше случаев (переменное количество аргументов и переменный тип возвращаемого значения):

#include <iostream>

template <typename R, typename ...T>
class Callback
{
public:
    virtual ~Callback() {}
    virtual R operator() (T... args) = 0;
};

template <typename R, typename ...T>
class FreeCallback : public Callback<R, T...>
{
public:
    FreeCallback(R(*func)(T...)) : func_(func) {}
    virtual R operator() (T... args) { return (*func_)(args...); }
private:
    R(*func_)(T...);
};

template <typename tClass, typename R, typename ...T>
class MemberCallback : public Callback<R, T...>
{
public:
    MemberCallback(tClass* instance, R (tClass::*memberfunction)(T...)) : instance_(instance), memberfunc_(memberfunction) {}
    virtual R operator() (T... args) { return (instance_->*memberfunc_)(args...); }
private:
    tClass * instance_;
    R (tClass::*memberfunc_)(T...);
};

class foo {
  public:
  Callback<int, int> *IntCallback;
  Callback<int, int, double, double> *IntDoubleDoubleCallback;
};

class blub {
  public:
  int func1(int i) {
    std::cout << "args: " << i << std::endl;
    return 1;
  }
  int func2(int i, double d1, double d2){
    std::cout << "args: " << i << " " << d1 << " " << d2 << std::endl;
    return 0;
  }
};

  int freeFunc1(int i) {
    std::cout << "args: " << i << std::endl;
    return 1;
  }
  int freeFunc2(int i, double d1, double d2){
    std::cout << "args: " << i << " " << d1 << " " << d2 << std::endl;
    return 0;
  }

int main() {
  foo f;
  blub b;
  f.IntCallback = new MemberCallback<blub, int, int>(&b, &blub::func1);
  f.IntDoubleDoubleCallback = new MemberCallback<blub, int, int, double, double>(&b, &blub::func2);

  Callback<int, int> *IntFreeCallback = new FreeCallback<int, int>(&freeFunc1);
  Callback<int, int, double, double> *IntDoubleDoubleFreeCallback = new FreeCallback<int, int, double, double>(&freeFunc2);

  int ret = (*IntFreeCallback)(42);
  std::cout << "ret freeFunc1: " << ret << std::endl;
  ret = (*IntDoubleDoubleFreeCallback)(42, 3.1415, 2.7182);
  std::cout << "ret freeFunc2: " << ret << std::endl;


  ret = (*f.IntCallback)(42);
  std::cout << "ret func1: " << ret << std::endl;
  ret = (*f.IntDoubleDoubleCallback)(42, 3.1415, 2.7182);
  std::cout << "ret func2: " << ret << std::endl;
  std::cout << "Hello World!\n";
  // cleanup not done here...
}
person krjw    schedule 03.01.2019