This is the first time that I have tried programming in such a general way using c++
. I have tackled this pattern before in other languages such as golang
, but this is my first attempt with cpp.
// class template for a subscriber that wishes to be informed of the
// occurrence of a particular event; the template parameter event type
// is the event of particular interest
template <typename EventType>
struct Subscriber {
virtual ~Subscriber() = default;
// default implementation of the event callback function
// not sure if it should be a pure virtual (for the compilation error notification)
// also possibly thinking about changing the signature to return a boolean
virtual void OnEventReceived(const EventType&) {};
};
Container template
// class template for a subscribers container; the template parameter
// subscriber type is a subscriber of some event type (i.e. some object
// that would like to be notified when some event is triggered)
template <typename SubscriberType>
struct Subscribers {
protected:
// function template for the _attach method which expects
// a reference to a subscriber of some event; the subscriber
// will be added to the pool of subscribers
template <typename EventSubscriber>
void _attach(EventSubscriber& s) {
subscribers_.emplace_back(
std::reference_wrapper<EventSubscriber>(s)
);
}
// function template for the _detach method which expects
// a reference to a subscriber of some event; the subscriber
// will be removed from the pool of subscribers
template <typename EventSubscriber>
void _detach(EventSubscriber& s) {
// erase-remove idiom
subscribers_.erase(
std::remove_if(
subscribers_.begin(),
subscribers_.end(),
// mixed feelings about this
[&s](auto i) { return &i.get() == &s; }
),
subscribers_.end()
);
}
// function template for the _publish method which expects
// a const reference to an event of some type; the subscribers
// will be notified of the event trigger via their callback
// implementation
template <typename EventType>
void _publish(const EventType& e) {
for (auto s : subscribers_)
s.get().OnEventReceived(e);
}
private:
// using std::reference_wrapper since the subscribers are not owned by
// the subscribers container, it is merely keeping a pointer to them in
// order to notify of event occurrences
std::vector<std::reference_wrapper<SubscriberType>> subscribers_;
};
Publisher template
// class template for an event publisher object; the template parameter
// event types is variadic so that the publisher instance may publish
// multiple type of events. The multiple inheritance extends the publisher
// to have a subscribers container for the specified event types.
template <typename... EventTypes>
struct Publisher : public Subscribers< Subscriber<EventTypes> >... {
// function template for the Attach method which expects an event type
// and a reference to some subscriber object that would like register
// for notification of the event type in question
template <typename EventType, typename EventSubscriber>
void Attach(EventSubscriber &s) {
this->Subscribers< Subscriber<EventType> >::_attach(s);
}
// function template for the Detach method which expects an event type
// and a reference to some subscriber object that would like unregister
// notifications of the event type in question
template <typename EventType, typename EventSubscriber>
void Detach(EventSubscriber &s) {
this->Subscribers< Subscriber<EventType> >::_detach(s);
}
// function template for the Publish method which expects a const reference
// to some type of event that will be broadcast to the currently registered
// subscribers of the event type in question
template <typename EventType>
void Publish(const EventType& e) {
this->Subscribers< Subscriber<EventType> >::_publish(e);
}
};
includes / compilation / questions
#include <vector>
#include <algorithm>
#include <functional>
// compilation
// g++ -std=c++17 main.cpp -o main
// Question 1:
// should the default implementation of the event callback function stay or
// should be a pure virtual (for the compilation error notification)
// Question 2:
// should attach and detach interfaces be changed to variadic? if so then should
// the recursion take place in Publisher template or the Container template?
// Question 3:
// how to break this up into headers and implementation to maintain a need to
// know basis with other code?
// Question 4:
// thread safety? My assumption is to use mutex in the attach detach methods.
Driver Code
This is just some example usage to see it working not really representative of a particular direction.
#include <iostream>
struct Event1 {
int x, y;
void print(std::ostream& os) const {
os << x << ' ' << y << '\n';
}
};
struct Event2 {
int a, b, c;
void print(std::ostream& os) const {
os << a << ' ' << b << ' ' << c << '\n';
}
};
struct SubOne : Subscriber<Event1>, Subscriber<Event2> {
void OnEventReceived(const Event2& e) override final {
e.print(std::cout);
};
void OnEventReceived(const Event1& e) override final {
e.print(std::cout);
};
};
int main(int argc, char** argv) {
Publisher<Event1, Event2> ePub{};
SubOne s1{};
SubOne s2{};
Event1 e1{2,3};
Event2 e2{30,21,55};
ePub.Attach<Event1>(s1);
ePub.Attach<Event1>(s2);
ePub.Attach<Event2>(s1);
ePub.Attach<Event2>(s2);
ePub.Publish(e1);
ePub.Publish(e2);
ePub.Detach<Event1>(s1);
ePub.Publish(e1);
return 0;
}
Love to hear any feedback, even outside of the scope the questions section.