I can't comment on the actual speed of the method that you are using, other than to say:
- Premature optimization does not usually give you what you expect.
- You should measure the performance contribution before you start slicing and dicing. If you know it won't work before hand, then you can search now for something better or go "suboptimal" for now but encapsulate it so it can be replaced.
If you are looking for a general event system that does not use std::function (but does use virtual methods), you can try this one:
Notifier.h
/*
The Notifier is a singleton implementation of the Subject/Observer design
pattern. Any class/instance which wishes to participate as an observer
of an event can derive from the Notified base class and register itself
with the Notiifer for enumerated events.
Notifier derived classes implement variants of the Notify function:
bool Notify(const NOTIFIED_EVENT_TYPE_T& event, variants ....)
There are many variants possible. Register for the message
and create the interface to receive the data you expect from
it (for type safety).
All the variants return true if they process the event, and false
if they do not. Returning false will be considered an exception/
assertion condition in debug builds.
Classes derived from Notified do not need to deregister (though it may
be a good idea to do so) as the base class destrctor will attempt to
remove itself from the Notifier system automatically.
The event type is an enumeration and not a string as it is in many
"generic" notification systems. In practical use, this is for a closed
application where the messages will be known at compile time. This allows
us to increase the speed of the delivery by NOT having a
dictionary keyed lookup mechanism. Some loss of generality is implied
by this.
This class/system is NOT thread safe, but could be made so with some
mutex wrappers. It is safe to call Attach/Detach as a consequence
of calling Notify(...).
*/
/* This is the base class for anything that can receive notifications.
*/
typedef enum
{
NE_MIN = 0,
NE_SETTINGS_CHANGED,
NE_UPDATE_COUNTDOWN,
NE_UDPATE_MESSAGE,
NE_RESTORE_FROM_BACKGROUND,
NE_MAX,
} NOTIFIED_EVENT_TYPE_T;
class Notified
{
public:
virtual bool Notify(NOTIFIED_EVENT_TYPE_T eventType, const uint32& value)
{ return false; };
virtual bool Notify(NOTIFIED_EVENT_TYPE_T eventType, const bool& value)
{ return false; };
virtual bool Notify(NOTIFIED_EVENT_TYPE_T eventType, const string& value)
{ return false; };
virtual bool Notify(NOTIFIED_EVENT_TYPE_T eventType, const double& value)
{ return false; };
virtual ~Notified();
};
class Notifier : public SingletonDynamic<Notifier>
{
public:
private:
typedef vector<NOTIFIED_EVENT_TYPE_T> NOTIFIED_EVENT_TYPE_VECTOR_T;
typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T> NOTIFIED_MAP_T;
typedef map<Notified*,NOTIFIED_EVENT_TYPE_VECTOR_T>::iterator NOTIFIED_MAP_ITER_T;
typedef vector<Notified*> NOTIFIED_VECTOR_T;
typedef vector<NOTIFIED_VECTOR_T> NOTIFIED_VECTOR_VECTOR_T;
NOTIFIED_MAP_T _notifiedMap;
NOTIFIED_VECTOR_VECTOR_T _notifiedVector;
NOTIFIED_MAP_ITER_T _mapIter;
// This vector keeps a temporary list of observers that have completely
// detached since the current "Notify(...)" operation began. This is
// to handle the problem where a Notified instance has called Detach(...)
// because of a Notify(...) call. The removed instance could be a dead
// pointer, so don't try to talk to it.
vector<Notified*> _detached;
int32 _notifyDepth;
void RemoveEvent(NOTIFIED_EVENT_TYPE_VECTOR_T& orgEventTypes, NOTIFIED_EVENT_TYPE_T eventType);
void RemoveNotified(NOTIFIED_VECTOR_T& orgNotified, Notified* observer);
public:
virtual void Reset();
virtual bool Init() { Reset(); return true; }
virtual void Shutdown() { Reset(); }
void Attach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
// Detach for a specific event
void Detach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType);
// Detach for ALL events
void Detach(Notified* observer);
// This template function (defined in the header file) allows you to
// add interfaces to Notified easily and call them as needed. Variants
// will be generated at compile time by this template.
template <typename T>
bool Notify(NOTIFIED_EVENT_TYPE_T eventType, const T& value)
{
if(eventType < NE_MIN || eventType >= NE_MAX)
{
throw std::out_of_range("eventType out of range");
}
// Keep a copy of the list. If it changes while iterating over it because of a
// deletion, we may miss an object to update. Instead, we keep track of Detach(...)
// calls during the Notify(...) cycle and ignore anything detached because it may
// have been deleted.
NOTIFIED_VECTOR_T notified = _notifiedVector[eventType];
// If a call to Notify leads to a call to Notify, we need to keep track of
// the depth so that we can clear the detached list when we get to the end
// of the chain of Notify calls.
_notifyDepth++;
// Loop over all the observers for this event.
// NOTE that the the size of the notified vector may change if
// a call to Notify(...) adds/removes observers. This should not be a
// problem because the list is a simple vector.
bool result = true;
for(int idx = 0; idx < notified.size(); idx++)
{
Notified* observer = notified[idx];
if(_detached.size() > 0)
{ // Instead of doing the search for all cases, let's try to speed it up a little
// by only doing the search if more than one observer dropped off during the call.
// This may be overkill or unnecessary optimization.
switch(_detached.size())
{
case 0:
break;
case 1:
if(_detached[0] == observer)
continue;
break;
default:
if(std::find(_detached.begin(), _detached.end(), observer) != _detached.end())
continue;
break;
}
}
result = result && observer->Notify(eventType,value);
assert(result == true);
}
// Decrement this each time we exit.
_notifyDepth--;
if(_notifyDepth == 0 && _detached.size() > 0)
{ // We reached the end of the Notify call chain. Remove the temporary list
// of anything that detached while we were Notifying.
_detached.clear();
}
assert(_notifyDepth >= 0);
return result;
}
/* Used for CPPUnit. Could create a Mock...maybe...but this seems
* like it will get the job done with minimal fuss. For now.
*/
// Return all events that this object is registered for.
vector<NOTIFIED_EVENT_TYPE_T> GetEvents(Notified* observer);
// Return all objects registered for this event.
vector<Notified*> GetNotified(NOTIFIED_EVENT_TYPE_T event);
};
Notifier.cpp
#include "Notifier.h"
void Notifier::Reset()
{
_notifiedMap.clear();
_notifiedVector.clear();
_notifiedVector.resize(NE_MAX);
_detached.clear();
_notifyDepth = 0;
}
void Notifier::Attach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType)
{
if(observer == NULL)
{
throw std::out_of_range("observer == NULL");
}
if(eventType < NE_MIN || eventType >= NE_MAX)
{
throw std::out_of_range("eventType out of range");
}
_mapIter = _notifiedMap.find(observer);
if(_mapIter == _notifiedMap.end())
{ // Registering for the first time.
NOTIFIED_EVENT_TYPE_VECTOR_T eventTypes;
eventTypes.push_back(eventType);
// Register it with this observer.
_notifiedMap[observer] = eventTypes;
// Register the observer for this type of event.
_notifiedVector[eventType].push_back(observer);
}
else
{
NOTIFIED_EVENT_TYPE_VECTOR_T& events = _mapIter->second;
bool found = false;
for(int idx = 0; idx < events.size() && !found; idx++)
{
if(events[idx] == eventType)
{
found = true;
break;
}
}
if(!found)
{
events.push_back(eventType);
_notifiedVector[eventType].push_back(observer);
}
}
}
void Notifier::RemoveEvent(NOTIFIED_EVENT_TYPE_VECTOR_T& eventTypes, NOTIFIED_EVENT_TYPE_T eventType)
{
int foundAt = -1;
for(int idx = 0; idx < eventTypes.size(); idx++)
{
if(eventTypes[idx] == eventType)
{
foundAt = idx;
break;
}
}
if(foundAt >= 0)
{
eventTypes.erase(eventTypes.begin()+foundAt);
}
}
void Notifier::RemoveNotified(NOTIFIED_VECTOR_T& notified, Notified* observer)
{
int foundAt = -1;
for(int idx = 0; idx < notified.size(); idx++)
{
if(notified[idx] == observer)
{
foundAt = idx;
break;
}
}
if(foundAt >= 0)
{
notified.erase(notified.begin()+foundAt);
}
}
void Notifier::Detach(Notified* observer, NOTIFIED_EVENT_TYPE_T eventType)
{
if(observer == NULL)
{
throw std::out_of_range("observer == NULL");
}
if(eventType < NE_MIN || eventType >= NE_MAX)
{
throw std::out_of_range("eventType out of range");
}
_mapIter = _notifiedMap.find(observer);
if(_mapIter != _notifiedMap.end())
{ // Was registered
// Remove it from the map.
RemoveEvent(_mapIter->second, eventType);
// Remove it from the vector
RemoveNotified(_notifiedVector[eventType], observer);
// If there are no events left, remove this observer completely.
if(_mapIter->second.size() == 0)
{
_notifiedMap.erase(_mapIter);
// If this observer was being removed during a chain of operations,
// cache them temporarily so we know the pointer is "dead".
_detached.push_back(observer);
}
}
}
void Notifier::Detach(Notified* observer)
{
if(observer == NULL)
{
throw std::out_of_range("observer == NULL");
}
_mapIter = _notifiedMap.find(observer);
if(_mapIter != _notifiedMap.end())
{
// These are all the event types this observer was registered for.
NOTIFIED_EVENT_TYPE_VECTOR_T& eventTypes = _mapIter->second;
for(int idx = 0; idx < eventTypes.size();idx++)
{
NOTIFIED_EVENT_TYPE_T eventType = eventTypes[idx];
// Remove this observer from the Notified list for this event type.
RemoveNotified(_notifiedVector[eventType], observer);
}
_notifiedMap.erase(_mapIter);
}
// If this observer was being removed during a chain of operations,
// cache them temporarily so we know the pointer is "dead".
_detached.push_back(observer);
}
Notified::~Notified()
{
Notifier::Instance().Detach(this);
}
// Return all events that this object is registered for.
vector<NOTIFIED_EVENT_TYPE_T> Notifier::GetEvents(Notified* observer)
{
vector<NOTIFIED_EVENT_TYPE_T> result;
_mapIter = _notifiedMap.find(observer);
if(_mapIter != _notifiedMap.end())
{
// These are all the event types this observer was registered for.
result = _mapIter->second;
}
return result;
}
// Return all objects registered for this event.
vector<Notified*> Notifier::GetNotified(NOTIFIED_EVENT_TYPE_T event)
{
return _notifiedVector[event];
}
NOTES:
- You must call init() on the class before using it.
- You don't have to use it as a singleton, or use the singleton template I used here. That is just to get a reference/init/shutdown mechanism in place.
- This is from a larger code base. You can find some other examples on github here.