Как я определяю метод класса, который принимает указатель на производную функцию-член класса в качестве параметра в C ?

#c #templates #function-pointers #observer-pattern

#c #шаблоны #указатели на функции #наблюдатель-шаблон

Вопрос:

Я использую реализацию C с сигнальным слотом, найденную здесь: https://github.com/pbhogan/Signals для реализации прослушивателей событий в библиотеке пользовательского интерфейса, которую я разрабатываю.

Пользовательский API выглядит следующим образом:

 class DialEventListener : public EventListener {

    void handleEvent(Event event){
        …
        cout << "Event on" << event.source.name << end;
    }
}
int main(){
    Interactor dial1;
    dial1.addListener(ROTATE_EVENT, DialEventListener());
    … 
}
  

Чтобы определить прослушиватель событий, пользователь должен создать подкласс EventListener для создания пользовательского обработчика событий. Но в реализации Interactor::addListener() , где я использую сигналы.в коде вы должны предоставить указатель на функцию-член, а также указатель экземпляра класса для подключения делегата к некоторому сигналу.

Ниже приведен фрагмент кода, показывающий, как я пытаюсь использовать сигналы для реализации прослушивателей событий. Я использую сопоставление от EventType s до Signal для учета взаимодействия, которое запускает несколько типов событий.

 typedef map< EventType, Signal1<Event> > EventTypeListenerMap;
class Interactor {

private:
   EventTypeListenerMap listener_map;

public:
   void TriggerEvent(Event e){
       Signal1<Event> signal = listener_map[e.type];
       signal(e);
   }

   void addListener(EventType e_type, EventListener listener){
      …
          //Find the signal for a particular kind of event.
      Signal1<TUIKitEvent> signal = listener_map[e_type];
          //Plug a delegate into that signal's slot
      signal.Connect( amp;listener, amp;EventListener::handleEvent); 
   }
   void addListener(EventType e_type, EventListener listener, void (EventListener::*handler)(Event)){
      …
      Signal1<TUIKitEvent> signal = listener_map[e_type];
      signal.Connect( amp;listener, handler); 
   }

}    

class EventListener  {
    virtual void handleEvent(Event event){
    }
}
  

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

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

1. Я не думаю, что вопрос действительно ясен… почему вы хотите передать указатель на функцию-член производному методу, а не просто функции-члену базового типа, и чтобы динамическая отправка разрешала окончательное переопределение для вас? Может быть, вам следует добавить небольшой фрагмент кода, указывающий, как выглядит пользовательский код, и поведение, которое вы хотите от него…

Ответ №1:

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

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

 // user code
Interactor dial;
DialEventListener dial_listener;
dial.addListener( ROTATE_EVENT, dial_listener );

// implementation
void Interactor::addListener( EventType event, EventListener amp; listener ) {
   Signal1<TUIKitEvent> amp; sgn = listener_map[event];
   sgn.Connect( amp;listener, amp;EventListener::handleEvent );
};
  

Обратите внимание на пару изменений: listener аргумент передается по ссылке, а не по значению. Если вы передадите по значению, вы получите нарезку ( EventListener будет скопирован только подобъект, и он не будет DialEventListener объектом внутри addListener . Передавая ссылку на базовый тип, вы можете использовать его в качестве базового, пока объект сохраняет свою идентичность.

Это, в свою очередь, требует, чтобы передаваемый вами объект не был временным (вы не можете использовать в DialEventListener() качестве второго аргумента addListener , поскольку время жизни временного значения заканчивается в конце полного выражения, и это означает, что у вас будет висячая ссылка (в вашей текущей реализации у вас есть висячий указатель внутри сигналареализация, которая, скорее всего, вызовет UB после срабатывания сигнала).

Кроме того, внутри addListener вы не хотите копировать сигнал, сохраненный на карте, а скорее изменять его, поэтому, опять же, вы должны использовать ссылку, а не значение при доступе к сигналу на карте.

Документация вашего класса должна быть достаточно четкой в том смысле, что время жизни переданного объекта должно пережить Interactor (или разрешить отмену регистрации, чтобы клиент мог удалить обработчик из Interactor до уничтожения обратного вызова (опять же, если вы этого не сделаете, вы закончите в UB)

Другой подход заключается в выполнении стирания типов, я никогда не использовал эту конкретную библиотеку signals, поэтому я играю на слух здесь. Библиотека сигналов, которую вы используете (вероятно), реализует стирание типов внутри, но вам нужно иметь возможность пересылать точный тип, пока библиотека не сможет выполнить стирание, или использовать другую библиотеку стирания типов в интерфейсе.

 // Transferring the exact type to the signal lib:
class Interactor {
// ...
public:
   template <typename Listener>
   void addListener( EventType event, Listener amp; listener, void (Listener::*callback)(Event) ) {
      Signal1<TUIKitEvent> amp; sgn = listener_map[event];
      sgn.connect( amp;listener, callback );
   }
//...
  

Использование другой библиотеки стирания типов в интерфейсе может быть или не быть возможным в зависимости от семантики connect метода в вашей реализации signals. Если он копирует первый аргумент, то вы, вероятно, можете обойтись std::function :

 class Interactor {
//...
public:
   void addListener( EventType event, std::function< void (Event) > callback ) {
       Signal1<TUIKitEvent> amp; sgn = listener_map[event];
       sgn.connect( callback, amp;std::function<void(Event)>::operator() );
   }
//...
};
  

Предполагая, что библиотека скопирует первый аргумент и сохранит его собственную копию внутри (т. Е. Предполагая, что он ведет себя как-то как boost::signal библиотека). На стороне пользователя им придется выполнить удаление типа:

 struct AnotherListener {   // no need to explicitly derive from EventListner now!
   void method( Event e ) {}
};
int main() {
   Interactor dial;
   AnotherEventListener listener;
   dial.addListener( dial, std::bind( amp;AnotherListener::method, amp;listener ) );
}
  

Точный синтаксис bind может быть не таким, я никогда не использовал его из нового стандарта в библиотеке boost, которым он был бы boost::bind( amp;AnotherListener::method, amp;listener, _1 );

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

1. Оба метода работают. Моя первоначальная проблема заключалась в том, что я не передавал слушателя по ссылке, поэтому он вызывал EventListener::handleEvent(), а не DialEventListener::handleEvent() . Я наткнулся на что-то похожее на 2-й подход, где я создал шаблон функции следующим образом: template<class T, class U>addListener(EventType, T listener, void (U::handleEvent)(Event)) . Спасибо за ваш ответ.

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

3. Вероятно, это была какая-то другая ошибка с моей стороны, которую я в конечном итоге устранил, но удаление библиотеки имеет два параметра класса, и поскольку указатели на функции-члены зависят от класса, это могло быть моей проблемой: См. template< class X, class Y > void Connect( Y * obj, void (X::*func)() ) { delegateList.insert( MakeDelegate( obj, func ) ); }

4. Как я уже сказал, я не знаю эту библиотеку, но я использовал boost::signal library раньше (она была изменена на boost::signal2 IIRC), и она была довольно гибкой в том, что она позволяла (путем объединения библиотеки с boost::bind (в C 11 вы можете использовать std::bind )… просто говорю…