I find Observer pattern
a very useful tool to decrease coupling between modules of a system.
Boost provides Signals2 which can be used for that purpose. But
adding Signal2 objects to arbitrary classes is not very convenient,
because your must manually implement RegisterEvent/FireEvent
functions.
What I was looking for
is a reusable mixin which could add “Observable” behavior to
arbitrary class with minimal hassle. Ideally, the user of mixing must
only provide event table which contains event tags and handler
signatures, e.g. (in pseudocode):
EventTable {
EventA: void(),
EventB: int(bool),
...
}
I came up with working
proof of concept which uses CRTP-based mixin and traits class for event table
declaration. I'm new to template meta-programming, so I'm looking
forward to comments about possible improvements.
In particular, I'm not
very satisfied with syntax boost::fusion::pair<Show,
boost::optional<boost::signals2::signal<void()>>> (see attachement). I think, I can do
better by allowing user to specify just boost::fusion::pair<Show, void()> and
then generate proper type with mpl::transform and/or
fusion::transform. This way is shorter and implementation details
(optional, signals2) are hidden from the user.
The mixin code (full code with example usage in attachment):
#include <utility>
#include <boost/fusion/container/map.hpp>
#include <boost/fusion/sequence/intrinsic/at_key.hpp>
#include <boost/fusion/sequence/intrinsic/value_at_key.hpp>
#include <boost/optional.hpp>
#include <boost/signals2.hpp>
#include <boost/utility/in_place_factory.hpp>
//*****************************************************************************
// EventSource mixin
//*****************************************************************************
// event source traits base (concrete event source must create specialization)
template<typename ConcreteEventSource> struct Events;
// event source mixin (using CRTP)
template<typename ConcreteEventSource> class EventSource {
public:
typedef ::Events<ConcreteEventSource> Events;
private:
typedef typename Events::EventTable EventTable;
// helper type generator
template<typename Event> struct GetType {
// boost::optional type
typedef typename boost::fusion::result_of::
value_at_key<EventTable, Event>::type SignalWrapper;
// boost::signal type for particular boost::optional type
typedef typename SignalWrapper::value_type Signal;
// slot return type for particular boost::signal type
typedef typename Signal::result_type SignalReturnType;
};
public:
// connect event handler
template<typename Event, typename Handler>
void Connect(Handler&& handler) {
GetSignal<Event>()->connect(std::move(handler));
}
protected:
// generate nullary signal
template<typename Event>
typename GetType<Event>::SignalReturnType FireEvent() {
return GetSignal<Event>()->operator()();
}
// generate unary signal
template<typename Event, typename Arg1>
typename GetType<Event>::SignalReturnType FireEvent(Arg1 arg1) {
return GetSignal<Event>()->operator()(arg1);
}
// ... more overloads (MSVC does not support variadic templates)
private:
// creates signal if not exists, returns reference to it
template<typename Event>
typename GetType<Event>::SignalWrapper& GetSignal() {
auto& signal = boost::fusion::at_key<Event>(signals_);
if (!signal)
signal = boost::in_place();
return signal;
}
EventTable signals_;
};