обработка указателя на функции-члены в иерархии в C ++ - PullRequest
2 голосов
/ 26 мая 2010

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

class EH { // EventHandler
   virtual void something(); // just to make sure we get RTTI
public:
  typedef void (EH::*func_t)();
protected:
  func_t funcs_d[10];
protected:
  void register_handler(int event_num, func_t f) {
    funcs_d[event_num] = f;
  }
public:
  void handle_event(int event_num) {
    (this->*(funcs_d[event_num]))();
  }
};

Затем пользователи должны извлечь другие классы из этого и предоставить обработчики:

class DEH : public EH {
public:
  typedef void (DEH::*func_t)();
  void handle_event_5();
  DEH() {
     func_t f5 = &DEH::handle_event_5;
     register_handler(5, f5); // doesn't compile
     ........
  }
};

Этот код не будет компилироваться, поскольку DEH :: func_t не может быть преобразовано в EH :: func_t. Это имеет смысл для меня. В моем случае преобразование безопасно, так как объект в this действительно DEH. Поэтому я бы хотел что-то подобное:

void EH::DEH_handle_event_5_wrapper() {
  DEH *p = dynamic_cast<DEH *>(this);
  assert(p != NULL);
  p->handle_event_5();
}

и затем вместо

     func_t f5 = &DEH::handle_event_5;
     register_handler(5, f5); // doesn't compile

в DEH :: DEH () ставить

     register_handler(5, &EH::DEH_handle_event_5_wrapper); 

Итак, наконец, вопрос (у меня ушло достаточно много времени ...): Есть ли способ автоматически создавать эти оболочки (например, EH::DEH_handle_event_5_wrapper)? Или сделать что-то подобное? Какие еще варианты решения этой ситуации существуют?

Спасибо.

Ответы [ 3 ]

2 голосов
/ 27 мая 2010

Вместо создания оболочки для каждого обработчика во всех производных классах (конечно, даже без удаленного подхода), вы можете просто использовать static_cast для преобразования DEH::func_t в EH::func_t. Указатели-члены контравариантны : они естественным образом преобразуются вниз по иерархии, и их можно вручную преобразовать вверх по иерархии, используя static_cast (в отличие от обычных указателей объектов, которые ковариантны ).

Ситуация, с которой вы имеете дело, является именно той причиной, по которой функциональность static_cast была расширена, чтобы разрешить передачу указателя на член. Более того, нетривиальная внутренняя структура указателя на функцию-член также реализована именно таким образом, чтобы правильно обрабатывать такие ситуации.

Итак, вы можете просто сделать

DEH() {
   func_t f5 = &DEH::handle_event_5;
   register_handler(5, static_cast<EH::func_t>(f5));
   ........
}

Я бы сказал, что в этом случае нет смысла определять имя typedef DEH::func_t - это довольно бесполезно. Если вы удалите определение DEH::func_t, типичный регистрационный код будет выглядеть следующим образом

DEH() {
   func_t f5 = static_cast<func_t>(&DEH::handle_event_5); 
   // ... where `func_t` is the inherited `EH::func_t`
   register_handler(5, f5);
   ........
}

Чтобы сделать его более элегантным, вы можете предоставить оболочку для register_handler в DEH или использовать другие средства (макрос? Шаблон?), Чтобы скрыть приведение.

Этот метод не предоставляет никаких средств для проверки правильности указателя обработчика в момент вызова (как вы могли бы сделать с dynamic_cast в версии на основе оболочки). Однако я не знаю, насколько вы заботитесь о том, чтобы этот чек был на месте. Я бы сказал, что в этом контексте это на самом деле ненужно и чрезмерно.

0 голосов
/ 27 мая 2010

Почему бы просто не использовать виртуальные функции? Что-то вроде

class EH {
public:
  void handle_event(int event_num) {

    // Do any pre-processing...

    // Invoke subclass hook
    subclass_handle_event( event_num );

    // Do any post-processing...
  }
private:
  virtual void subclass_handle_event( int event_num ) {}
};

class DEH : public EH {
public:
  DEH() { }
private:
  virtual void subclass_handle_event( int event_num ) {
     if ( event_num == 5 ) {
        // ...
     }
  }
};
0 голосов
/ 27 мая 2010

Вы действительно не должны делать это таким образом. Проверьте boost :: bind

http://www.boost.org/doc/libs/1_43_0/libs/bind/bind.html

Разработка

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

Смысл boost::bind и тому подобного заключается в создании объектов из указателей функций, что позволяет вам использовать возможности объектно-ориентированного языка. Таким образом, реализация, основанная на boost::bind с вашим дизайном в качестве отправной точки, будет выглядеть примерно так:

struct EventCallback
{
    virtual ~EventCallback() { }
    virtual void handleEvent() = 0;
};

template <class FuncObj>
struct EventCallbackFuncObj : public IEventCallback
{
    EventCallbackT(FuncObj funcObj) :
        m_funcObj(funcObj) { }
    virtual ~EventCallbackT() { }

    virtual void handleEvent()
    {
        m_funcObj();
    }

    private:
        FuncObj m_funcObj;
};

Тогда ваша register_handler функция выглядит примерно так:

  void register_handler(int event_num, EventCallback* pCallback) 
  {
      m_callbacks[event_num] = pCallback;
  }

А ваш регистрационный звонок хотел бы:

register_handler(event, 
    new EventCallbackFuncObj(boost::bind(&DEH::DEH_handle_event_5_wrapper, this)));

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

...