C ++: функции-члены класса как обратные вызовы событий - PullRequest
6 голосов
/ 03 марта 2011

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

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

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

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

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

Ответы [ 7 ]

7 голосов
/ 03 марта 2011

Да, это возможно. 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

3 голосов
/ 03 марта 2011

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

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

См. Это официальное повышение учебное пособие .

2 голосов
/ 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;
};

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

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 ();

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

1 голос
/ 03 марта 2011

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

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

0 голосов
/ 03 января 2019

Я использую 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...
}
0 голосов
/ 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 с вашим обработчиком. Возможно, вам придется обернуть это в структуру, содержащую ссылки, если вы ограничены в том, что может быть возвращено.

0 голосов
/ 03 марта 2011

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

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

...