patterncppMinor
Simple wrapper for member function pointers with known signature
Viewed 0 times
simplewithsignaturefunctionmemberwrapperforpointersknown
Problem
I always wanted to know how to pass member functions as arguments, and then I stumbled across templates that could automatically deduce types, and so I rejoiced! This is used in a Publisher-Subscriber system for a game server. I wanted the subscribing to individual packet 'types' (i.e. by enum) to be simple at the calling site.
Used thus:
Are there any glaring flaws or possible improvements, here? Perhaps something that's incredibly unsafe?
For context, the function pointer is invoked thus, where the publisher stores the callbacks in the form
```
void NetworkPublisher::NotifySubscribers(int a
/**
* Wrapper struct for callbacks fired when RakNet::Packet*s arrive.
* TargetObj is used to identify this callback by which object it is bound to.
*/
struct PacketCallback
{
template
PacketCallback(void(Obj::*mf)(const RakNet::Packet*), Obj* obj)
: TargetObj(obj)
{
Func = std::bind(mf, obj, std::placeholders::_1);
}
std::function Func;
void* TargetObj = nullptr;
};Used thus:
class ClientManagementTest : public INetworkSubscriber
{
public:
virtual void Subscribe(NetworkPublisher* a_networkPublisher) override
{
a_networkPublisher->Register(ID_NEW_INCOMING_CONNECTION,
PacketCallback(&ClientManagement::OnClientConnected, this));
a_networkPublisher->Register(ID_CONNECTION_LOST,
PacketCallback(&ClientManagement::OnClientDisconnected, this));
a_networkPublisher->Register(ID_DISCONNECTION_NOTIFICATION,
PacketCallback(&ClientManagement::OnClientDisconnected, this));
}
virtual void Unsubscribe(NetworkPublisher* a_networkPublisher) override
{
a_networkPublisher->UnregisterAll(this);
}
void OnClientConnected(const RakNet::Packet* a_packet)
{
printf("Someone connected\n");
}
void OnClientDisconnected(const RakNet::Packet* a_packet)
{
printf("Someone disconnected\n");
}
};Are there any glaring flaws or possible improvements, here? Perhaps something that's incredibly unsafe?
For context, the function pointer is invoked thus, where the publisher stores the callbacks in the form
std::map> m_subscribers;:```
void NetworkPublisher::NotifySubscribers(int a
Solution
Sorry for the brief answer, typing on phone.
Your
And then simply do:
This way you dont need the
Side note: You might want to consider using smart pointers instead of raw pointers. Or references if null is not an allowed argument.
Edit: In reply to comments, this is one way I would consider of doing it:
Note that by using
The above code is untested pseudocode. Some assembly may be required :)
Your
NetworkPublisher should have Register(int signal, std::function callback)And then simply do:
publisher.Register(WHATEVER_SIGNAL, std::bind(&MyClass::MySignalCallback, this));This way you dont need the
PacketCallback class and your subscriber interface is more generally usable as you can now pass lamdas and free functions as well. Further more your NotifySubscribers will simplify a bit.Side note: You might want to consider using smart pointers instead of raw pointers. Or references if null is not an allowed argument.
Edit: In reply to comments, this is one way I would consider of doing it:
enum class SignalType{
NewConnection,
ConnectionLost
};
const std::array AllSignalTypes = {
SignalType::NewConnection,
SignalType::ConnectionLost};
class ISubscriber{
public:
virtual bool supportsSignal(SignalType signalType) const = 0;
virtual void signal(SignalType signalType, const RakNet::Packet& packet) = 0;
}
class Publisher{
public:
void registerSubscriber(const std::shared_ptr& subscriber){
for(auto& type : AllSignalTypes){
if(subscriber->supportsSignal(type)){
// TODO: Check for duplicates or change to std::unordered_set
subscribers[type].emplace_back(subscriber);
}
}
}
void signal(SignalType signalType, const RakNet::Packet& packet){
forEachLiveSubscriber(signalType, [&packet](const std::shared_ptr& subscriber){
subscriber->signal(signalType, packet);
return true;
});
}
void unregisterAll(const ISubscriber& subscriber){
// Technically not necessary but for good measure
for(auto& type : AllSignalTypes){
forEachLiveSubscriber(signalType, [&packet](const std::shared_ptr& sharedSubscriber){
return sharedSubscriber.get() != &subscriber;
});
}
}
private:
std::unordered_map>> subscribers;
template
void forEachLiveSubscriber(SignalType signalType, Callable&& callback){
auto& signalSubscribers = subscribers[signalType];
auto it = signalSubscribers.begin();
while(it != signalSubscribers.end()){
auto& strongPtr = it->lock();
if(strongPtr && callback(strongPtr)){
it++;
}
else{
// The subscriber has destructed without unregistering,
// Or the callback requested us to remove it.
it = signalSubscribers.erase(it);
}
}
}
}
class MySubscriber : public ISubscriber{
bool supportsSignal(SignalType signalType) const override{
return signalType == NewConnection || signalType == ConnectionLost;
}
void signal(SignalType signalType, const RakNet::Packet& packet) override{
switch(signalType){
case NewConnection:
std::cout<<"Someone connected"<<std::endl;
break;
case ConnectionLost:
std::cout<<"Someone lost connection"<<std::endl;
break;
}
}
}Note that by using
shared_ptr we get access to weak_ptr which in turn allows lazy garbage collection of the subscribers. In turn this means that a subscriber doesn't have to unregister if all shared pointers to it just simply die. The above code is untested pseudocode. Some assembly may be required :)
Code Snippets
publisher.Register(WHATEVER_SIGNAL, std::bind(&MyClass::MySignalCallback, this));enum class SignalType{
NewConnection,
ConnectionLost
};
const std::array<SignalType,2> AllSignalTypes = {
SignalType::NewConnection,
SignalType::ConnectionLost};
class ISubscriber{
public:
virtual bool supportsSignal(SignalType signalType) const = 0;
virtual void signal(SignalType signalType, const RakNet::Packet& packet) = 0;
}
class Publisher{
public:
void registerSubscriber(const std::shared_ptr<ISubscriber>& subscriber){
for(auto& type : AllSignalTypes){
if(subscriber->supportsSignal(type)){
// TODO: Check for duplicates or change to std::unordered_set<weak_ptr>
subscribers[type].emplace_back(subscriber);
}
}
}
void signal(SignalType signalType, const RakNet::Packet& packet){
forEachLiveSubscriber(signalType, [&packet](const std::shared_ptr<ISubscriber>& subscriber){
subscriber->signal(signalType, packet);
return true;
});
}
void unregisterAll(const ISubscriber& subscriber){
// Technically not necessary but for good measure
for(auto& type : AllSignalTypes){
forEachLiveSubscriber(signalType, [&packet](const std::shared_ptr<ISubscriber>& sharedSubscriber){
return sharedSubscriber.get() != &subscriber;
});
}
}
private:
std::unordered_map<SignalType, std::vector<std::weak_ptr<ISubscriber>>> subscribers;
template<typename Callable>
void forEachLiveSubscriber(SignalType signalType, Callable&& callback){
auto& signalSubscribers = subscribers[signalType];
auto it = signalSubscribers.begin();
while(it != signalSubscribers.end()){
auto& strongPtr = it->lock();
if(strongPtr && callback(strongPtr)){
it++;
}
else{
// The subscriber has destructed without unregistering,
// Or the callback requested us to remove it.
it = signalSubscribers.erase(it);
}
}
}
}
class MySubscriber : public ISubscriber{
bool supportsSignal(SignalType signalType) const override{
return signalType == NewConnection || signalType == ConnectionLost;
}
void signal(SignalType signalType, const RakNet::Packet& packet) override{
switch(signalType){
case NewConnection:
std::cout<<"Someone connected"<<std::endl;
break;
case ConnectionLost:
std::cout<<"Someone lost connection"<<std::endl;
break;
}
}
}Context
StackExchange Code Review Q#148824, answer score: 4
Revisions (0)
No revisions yet.