I'd appreciate advice regarding both the implementation detail and ways to improve usage/performance/generic.
There are two parts: XDelegate
, which is I got from here, and XEvent
, which is the event implementation similar to C#.
#pragma once
#include <unordered_map>
//hashing function
//TODO: replace hashing with something else if it's too expensive
template<typename return_type, typename... params>
class XDelegate;
namespace std {
template<typename return_type, typename... params>
struct hash<XDelegate<return_type, params ...>> {
typedef return_type(*Type)(void* callee, params...);
static size_t hash_combine(size_t seed, size_t v)
{
return seed ^= v + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
size_t operator()(const XDelegate<return_type, params ...> &pDelegate) const
{
size_t a = std::hash<void*>{}(pDelegate.fpCallee);
size_t b = std::hash<Type>{}(pDelegate.fpCallbackFunction);
return hash_combine(a, b);
}
};
};
//nice generic variadic delegate from http://blog.coldflake.com/posts/C++-delegates-on-steroids/
template<typename return_type, typename... params>
class XDelegate
{
friend struct std::hash<XDelegate>;
typedef return_type(*Type)(void* callee, params...);
public:
XDelegate(void* callee, Type function)
: _fpCallee(callee)
, _fpCallbackFunction(function) {}
template <class T, return_type(T::*TMethod)(params...)>
static XDelegate from_function(T* callee)
{
XDelegate d(callee, &methodCaller<T, TMethod>);
return d;
}
return_type operator()(params... xs) const
{
return (*_fpCallbackFunction)(_fpCallee, xs...);
}
private:
void* _fpCallee;
Type _fpCallbackFunction;
template <class T, return_type(T::*TMethod)(params...)>
static return_type methodCaller(void* callee, params... xs)
{
T* p = static_cast<T*>(callee);
return (p->*TMethod)(xs...);
}
};
//C# style delegate-event for none ref class.
template<typename return_type, typename... params>
class XEvent
{
friend struct std::hash<XDelegate<return_type, params ...>>;
public:
size_t add(XDelegate<return_type, params ...> pDelegate)
{
size_t token = std::hash<XDelegate<return_type, params ...>>{}(pDelegate);
auto result = _delegates.insert_or_assign(token, pDelegate);
#ifdef _DEBUG
//if assignment took place, ie) there is a hash collision, throw exception.
if (!result.second)
{
throw std::exception("XEvent hash collision");
}
#endif
return token;
}
void remove(size_t pToken)
{
_delegates.erase(pToken);
}
size_t size() const
{
return _delegates.size();
}
//on hindsight, I don't think C# events have return value?
std::unordered_map<size_t, return_type> operator()(params... xs)const
{
std::unordered_map<size_t, return_type> result;
for (auto itr = _delegates.begin(); itr != _delegates.end(); ++itr)
{
result[itr->first] = itr->second(xs...);
}
//return map of event tokens mapped to the results.
return result;
}
private:
std::unordered_map<size_t, XDelegate<return_type, params ...>> _delegates;
};
template<typename... params>
class XEvent<void, params ...>
{
friend struct std::hash<XDelegate<void, params ...>>;
public:
size_t add(XDelegate<void, params ...> pDelegate)
{
size_t token = std::hash<XDelegate<void, params ...>>{}(pDelegate);
auto result = _delegates.insert_or_assign(token, pDelegate);
#ifdef _DEBUG
//if assignment took place, ie) there is a hash collision, throw exception.
if (!result.second)
{
throw std::exception("XEvent hash collision");
}
#endif
return token;
}
void remove(size_t pToken)
{
_delegates.erase(pToken);
}
size_t size() const
{
return _delegates.size();
}
void operator()(params... xs)const
{
for (auto itr = _delegates.begin(); itr != _delegates.end(); ++itr)
{
itr->second(xs...);
}
}
private:
std::unordered_map<size_t, XDelegate<void, params ...>> _delegates;
};
It is used like this:
_returnEventTokens.push_back(_returnEvent.add(XDelegate<int, std::wstring>::from_function<EventTester, &EventTester::returnEventCallback1>(_tester)));
_returnEventTokens.push_back(_returnEvent.add(XDelegate<int, std::wstring>::from_function<EventTester, &EventTester::returnEventCallback2>(_tester)));
_returnEventTokens.push_back(_returnEvent.add(XDelegate<int, std::wstring>::from_function<EventTester, &EventTester::returnEventCallback3>(_tester)));
_returnEvent(L"Return event called!\n");
for (auto itr = _noReturnEventTokens.begin(); itr != _noReturnEventTokens.end(); ++itr)
{
_noReturnEvent.remove(*itr);
}