Существует два подхода к обратным вызовам: либо вы предоставляете интерфейс с виртуальной функцией, которая будет вызываться, либо вы выполняете стирание типа , чтобы сделать его полностью универсальным. Во второй версии вы либо используете предварительно упакованное решение для type-erasure , либо вам нужно будет использовать некоторые шаблоны для его самостоятельной реализации.
Ожидаемый код пользователя, который вы показываете в первом блоке, следует первому подходу и достаточен для основных нужд. Но обратите внимание, что вы, вероятно, захотите использовать указатели или ссылки в нескольких местах, где вы передаете по значению:
// user code
Interactor dial;
DialEventListener dial_listener;
dial.addListener( ROTATE_EVENT, dial_listener );
// implementation
void Interactor::addListener( EventType event, EventListener & listener ) {
Signal1<TUIKitEvent> & sgn = listener_map[event];
sgn.Connect( &listener, &EventListener::handleEvent );
};
Обратите внимание на пару изменений: аргумент listener
передается по ссылке, а не по значению. Если вы передадите по значению, вы получите slicing (будет скопирован только подобъект EventListener
, и он не будет DialEventListener
объектом внутри addListener
. Передав ссылку на базовый тип, вы можете использовать его как базу, пока объект сохраняет свою идентичность.
Это, в свою очередь, требует, чтобы объект, который вы передаете, не был временным (вы не можете использовать DialEventListener()
в качестве второго аргумента для addListener
, так как время жизни временного объекта закончится в конце полного выражения, и это означает, что у вас будет свисающая ссылка (в вашей текущей реализации у вас есть свисающий указатель внутри реализации сигнала, который, скорее всего, вызовет UB после срабатывания сигнала).
Кроме того, внутри addListener
вы не хотите копировать сигнал, сохраненный на карте, а скорее изменять его, поэтому, опять же, вы должны использовать ссылку, а не значение при доступе к сигналу на карте.
Документация вашего класса должна быть достаточно явной, так как время жизни переданного объекта должно пережить Interactor
(или разрешить отмену регистрации , чтобы клиент мог удалить обработчик из Interactor
до того, как обратный вызов будет уничтожен (опять же, если вы этого не сделаете, вы закончите в UB)
Другой подход заключается в выполнении стирания типа , я никогда не использовал эту конкретную библиотеку сигналов, поэтому играю здесь на слух. Используемая вами библиотека сигналов (вероятно) реализует внутреннее стирание типов, но вам необходимо иметь возможность пересылать точный тип, пока библиотека не сможет выполнить стирание, или использовать в интерфейсе другую библиотеку стирания типов.
// Transferring the exact type to the signal lib:
class Interactor {
// ...
public:
template <typename Listener>
void addListener( EventType event, Listener & listener, void (Listener::*callback)(Event) ) {
Signal1<TUIKitEvent> & sgn = listener_map[event];
sgn.connect( &listener, callback );
}
//...
Использование другой библиотеки стирания типов в интерфейсе может или не может быть возможным в зависимости от семантики метода connect
в вашей реализации сигналов. Если он копирует первый аргумент, то вы, вероятно, можете обойтись с std::function
:
class Interactor {
//...
public:
void addListener( EventType event, std::function< void (Event) > callback ) {
Signal1<TUIKitEvent> & sgn = listener_map[event];
sgn.connect( callback, &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( &AnotherListener::method, &listener ) );
}
Точный синтаксис bind
может не быть таким, я никогда не использовал его из нового стандарта в библиотеке наддува, это было бы boost::bind( &AnotherListener::method, &listener, _1 );
...