Skip to content

Instantly share code, notes, and snippets.

@makomweb
Last active August 24, 2023 08:13
Show Gist options
  • Save makomweb/6982207 to your computer and use it in GitHub Desktop.
Save makomweb/6982207 to your computer and use it in GitHub Desktop.
Fun with C++: implementing a pub/sub scenario using std::bind and other standard facilities. The approach is pretty similar to the well known .NET event mechanism.
#include <iostream>
#include <map>
#include <algorithm>
#include <functional>
#include <memory>
using namespace std;
class EventArgs {
public:
virtual ~EventArgs() {}
};
class StringEventArgs : public EventArgs {
string payload_;
public:
explicit StringEventArgs(const string& payload) : payload_(payload) {}
const string& Payload() const { return payload_; }
};
class Event {
class Callback {
void* pSender_;
const EventArgs& args_;
public:
Callback(void* pSender, const EventArgs& args) : pSender_(pSender), args_(args) {}
void operator()(pair<long, function<void(void*, const EventArgs&)>> p) const {
p.second(pSender_, args_);
}
};
map<long, function<void(void*, const EventArgs&)>> callbacks_;
long token_ = 0;
public:
void operator()(void* pSender, const EventArgs& args) const {
for_each(callbacks_.begin(), callbacks_.end(), Callback(pSender, args));
}
long Subscribe(function<void(void*, const EventArgs&)> f) {
token_++;
callbacks_.insert(make_pair(token_, f));
return token_;
}
void Unsubscribe(long token) {
callbacks_.erase(token);
}
};
class Publisher {
Event event_;
string name_;
public:
explicit Publisher(const string& name) : name_(name) {}
const string& Name() const { return name_; }
void Publish(const string& message) {
event_(this, StringEventArgs(message));
}
long Register(function<void(void*, const EventArgs&)> f) {
return event_.Subscribe(f);
}
void Unregister(long token) {
event_.Unsubscribe(token);
}
};
class Subscriber {
string name_;
public:
explicit Subscriber(const string& name) : name_(name) {}
void OnEventReceived(void* pSender, const EventArgs& args) {
const StringEventArgs* const s = dynamic_cast<const StringEventArgs* const>(&args);
if (s == nullptr)
return;
if (pSender == nullptr)
return;
Publisher* p = static_cast<Publisher*>(pSender);
cout << name_.c_str() << " has received " << s->Payload().c_str() << " from " << p->Name().c_str() << endl;
}
};
namespace {
using namespace std::placeholders;
long Subscribe(Publisher& publisher, Subscriber& subscriber) {
return publisher.Register(bind(&Subscriber::OnEventReceived, &subscriber, _1, _2));
}
void Unsubscribe(Publisher& publisher, long token) {
publisher.Unregister(token);
}
}
class Subscription {
Publisher& publisher_;
long token_;
public:
Subscription(Publisher& publisher, long token) : publisher_(publisher), token_(token) {
}
~Subscription() {
publisher_.Unregister(token_);
}
};
int main() {
Publisher prod("Publisher");
Subscriber sub_1("Subscriber-1");
Subscriber sub_2("Subscriber-2");
long token_1 = Subscribe(prod, sub_1);
long token_2 = Subscribe(prod, sub_2);
prod.Publish("42");
Unsubscribe(prod, token_1);
prod.Publish("43");
Unsubscribe(prod, token_2);
prod.Publish("44");
return 0;
}
@antonhristozov
Copy link

antonhristozov commented Jan 6, 2023

This is cool. I templatized it and removed the c_str() references, and it can now work with arbitrary types, not just strings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment