Как преобразовать существующий интерфейс обратного вызова, чтобы использовать сигналы и слоты Boost
-
24-10-2019 - |
Вопрос
В настоящее время у меня есть класс, который может уведомить ряд других объектов с помощью обратных вызовов:
class Callback {
virtual NodulesChanged() =0;
virtual TurkiesTwisted() =0;
};
class Notifier
{
std::vector<Callback*> m_Callbacks;
void AddCallback(Callback* cb) {m_Callbacks.push(cb); }
...
void ChangeNodules() {
for (iterator it=m_Callbacks.begin(); it!=m_Callbacks.end(); it++) {
(*it)->NodulesChanged();
}
}
};
Я подумываю об изменении этого, чтобы использовать сигналы и слоты Boost, так как было бы полезно снизить вероятность того, что Callee удаляется, среди прочего. Однако, поскольку он обозначает сигналы Boost, кажется более ориентированными на работу с функциональными объектами. Как бы лучший способ адаптировать мой код по -прежнему использовать интерфейс обратного вызова, но использовать сигналы и слоты для борьбы с аспектами соединения и уведомлений?
Решение
По сравнению с моим другим ответом, это решение гораздо более общее и устраняет код шаблона:
#include <iostream>
#include <boost/bind.hpp>
#include <boost/signal.hpp>
///////////////////////////////////////////////////////////////////////////////
// GENERIC REUSABLE PART FOR ALL SUBJECTS
///////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
template <class CallbackType>
class CallbackInvoker
{
public:
virtual ~CallbackInvoker() {}
virtual void operator()(CallbackType* callback) const {};
};
//-----------------------------------------------------------------------------
template <class CallbackType, class Binding>
class BoundInvoker : public CallbackInvoker<CallbackType>
{
public:
BoundInvoker(const Binding& binding) : binding_(binding) {}
void operator()(CallbackType* callback) const {binding_(callback);}
private:
Binding binding_;
};
//-----------------------------------------------------------------------------
template <class CallbackType>
class CallbackSlot
{
public:
CallbackSlot(CallbackType* callback) : callback_(callback) {}
void operator()(const CallbackInvoker<CallbackType>& invoker)
{invoker(callback_);}
private:
CallbackType* callback_;
};
//-----------------------------------------------------------------------------
template <class CallbackType>
class Subject
{
public:
virtual ~Subject() {}
boost::signals::connection Connect(CallbackType* callback)
{return signal_.connect(CallbackSlot<CallbackType>(callback));}
protected:
template <class Binding> void Signal(const Binding& binding)
{
signal_(BoundInvoker<CallbackType,Binding>(binding));
}
private:
boost::signal<void (const CallbackInvoker<CallbackType>&)> signal_;
};
///////////////////////////////////////////////////////////////////////////////
// THIS PART SPECIFIC TO ONE SUBJECT
///////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------------
class MyCallback
{
public:
virtual ~MyCallback() {}
virtual void NodulesChanged() =0;
virtual void TurkiesTwisted(int arg) =0;
};
//-----------------------------------------------------------------------------
class FooCallback : public MyCallback
{
public:
virtual ~FooCallback() {}
void NodulesChanged() {std::cout << "Foo nodules changed\n";}
void TurkiesTwisted(int arg)
{std::cout << "Foo " << arg << " turkies twisted\n";}
};
//-----------------------------------------------------------------------------
class BarCallback : public MyCallback
{
public:
virtual ~BarCallback() {}
void NodulesChanged() {std::cout << "Bar nodules changed\n";}
void TurkiesTwisted(int arg)
{std::cout << "Bar " << arg << " turkies twisted\n";}
};
//-----------------------------------------------------------------------------
class MySubject : public Subject<MyCallback>
{
public:
void OnNoduleChanged()
{this->Signal(boost::bind(&MyCallback::NodulesChanged, _1));}
void OnTurkiedTwisted(int arg)
{this->Signal(boost::bind(&MyCallback::TurkiesTwisted, _1, arg));}
};
///////////////////////////////////////////////////////////////////////////////
// CLIENT CODE
///////////////////////////////////////////////////////////////////////////////
//-----------------------------------------------------------------------------
int main()
{
MySubject subject;
FooCallback fooCb;
BarCallback barCb;
subject.Connect(&fooCb);
subject.Connect(&barCb);
subject.OnNoduleChanged();
subject.OnTurkiedTwisted(42);
}
Ура для boost::bind
! :-)
Другие советы
Boost :: Сигналы довольно гибки, когда дело доходит до того, что вы связываете с сигналом. Вы можете использовать функциональный объект, но вы также можете просто использовать указатель функции или использовать Boost :: Bind, чтобы сделать практически любую функцию в функциональный объект. Вот как может выглядеть ваш пример, хотя могут быть лучшие способы.
#include <boost/signals.hpp>
class Notifier
{
public:
boost::signal< void() > NodulesChanged;
void ChangeNodules()
{
//Just call the signal and all connected slots will be called.
NodulesChanged();
}
};
Чтобы добавить обратный вызов, вы можете просто
void callback1()
{
//do callback stuff
}
void callback2()
{
//do callback stuff
}
int main()
{
Notifier n;
n.NodulesChanged.connect(&callback1);
n.NodulesChanged.connect(&callback2);
//calls callback1 & 2.
n.ChangeNodules();
}
Если вы хотите подключить функцию участника с аргументами в качестве слота, вы можете сделать что -то вроде этого:
class Notifier
{
public:
boost::signal< void ( double ) > ProgressSignal;
};
class OtherClass
{
public:
void UpdateProgress(double pct);
};
int main()
{
Notifier n;
OtherClass oc;
n.ProgressSignal.connect(boost::bind(&OtherClass::UpdateProgress, &oc, _1));
//Calls oc.UpdateProgress(0);
n.ProgressSignal(0);
}
Предупреждение: ничего из этого не было скомпилировано или проверено.
Это решение позволяет использовать то же самое signal
объект даже если Callback
Методы имеют разные подписи.
#include <iostream>
#include <boost/signal.hpp>
//------------------------------------------------------------------------------
class Callback
{
public:
virtual void NodulesChanged() =0;
virtual void TurkiesTwisted(int arg) =0;
};
//------------------------------------------------------------------------------
class FooCallback : public Callback
{
public:
void NodulesChanged() {std::cout << "Foo nodules changed\n";}
void TurkiesTwisted(int arg) {std::cout << "Foo " << arg << " turkies twisted\n";}
};
//------------------------------------------------------------------------------
class BarCallback : public Callback
{
public:
void NodulesChanged() {std::cout << "Bar nodules changed\n";}
void TurkiesTwisted(int arg) {std::cout << "Bar " << arg << " turkies twisted\n";}
};
//------------------------------------------------------------------------------
class CallbackInvoker
{
public:
virtual void operator()(Callback* callback) const {};
};
//------------------------------------------------------------------------------
class NoduleChangedInvoker : public CallbackInvoker
{
public:
void operator()(Callback* callback) const {callback->NodulesChanged();}
};
//------------------------------------------------------------------------------
class TurkiesTwistedInvoker : public CallbackInvoker
{
public:
TurkiesTwistedInvoker(int arg) : arg_(arg) {}
void operator()(Callback* callback) const {callback->TurkiesTwisted(arg_);}
private:
int arg_;
};
//------------------------------------------------------------------------------
class CallbackSlot
{
public:
CallbackSlot(Callback* callback) : callback_(callback) {}
void operator()(const CallbackInvoker& invoker) {invoker(callback_);}
private:
Callback* callback_;
};
//------------------------------------------------------------------------------
class Subject
{
public:
typedef boost::signal<void (const CallbackInvoker&)> SignalType;
boost::signals::connection Connect(Callback* callback)
{return signal_.connect(CallbackSlot(callback));}
void OnNoduleChanged() {signal_(NoduleChangedInvoker());}
void OnTurkiedTwisted(int arg) {signal_(TurkiesTwistedInvoker(arg));}
private:
SignalType signal_;
};
//------------------------------------------------------------------------------
int main()
{
Subject subject;
FooCallback fooCb;
BarCallback barCb;
subject.Connect(&fooCb);
subject.Connect(&barCb);
subject.OnNoduleChanged();
subject.OnTurkiedTwisted(42);
}
Это выводит:
Foo nodules changed
Bar nodules changed
Foo 42 turkies twisted
Bar 42 turkies twisted
CallbackSlot
объект функции, хранящийся в boost::signal
, и содержит указатель на бетон Callback
объект. Когда вы вызываете boost::signal
, вы должны пройти CallbackInvoker
конкретный объект, который объединяет любые аргументы обратного вызова и который знает, как вызвать соответствующий Callback
метод
Может быть способ избежать CallbackInvoker
Код с шаблоном с использованием boost.lamda, но я не очень знаком с этой библиотекой Boost.
Вы, вероятно, захотите использовать boost::shared_ptr<Callback>
вместо Callback*
Чтобы избежать утечек памяти и висящих указателей.