Introduction
There are many articles on CodeProject right now providing standard C++ versions of the C# delegate. Each one differs mostly with regard to what the author needed when developing it. For some, it was speed; for others, conformance to the standard, and yet others focused on portability, but almost all wanted a means to map a method of a class to an event in a generic manner.
My own start down this road involved a desire to produce my own framework that I could use to build apps in C++ for both Linux and Windows through a common API. Yes, I realize that there are many efforts out there that come close, but really what I sought was to have all of what I needed in one framework and integrated. Integrated so that events generated either from a windowing system like the Win32 message pump or from the communication layer like TCP/IP could be handled by the same class in a standardized manner. So, for the pedagogical experience as well as to truly have something that thinks like me, I decided to build my own.
So, what we start with is a need to have a handler to respond to events within the framework. I design my systems using more objects than static callbacks, so I just needed a way to map a method of a class object to a flyweight object, basically a functor, to use within the event system. So, this initial implementation hasn't been tested or enhanced to support static methods nor standard functions. If people actually start using this and request it, I'll add it in.
Background
I haven't profiled this code, so I don't know what the performance is. For a guaranteed performance, I would look at the works of Dan Clugston's and/or Sergey Ryazanov's if this implementation proves to be inefficient. They also cover all the vagaries of what's involved with different compiler bugs and standard vs. portability issues. What I'm providing is an encapsulated version of what Dan coins "The Horrible Hack".
This code was tested using VC++ 6.0 and VS 2003, as well as GCC 4.1.1 using Fedora Core 5. The handler uses the method that MFC does, which uses a union to mask the class type when casting a function pointer to a different type. This is the horrible hack that Dan eludes to, and I tried to reduce the shameless subversion of the C++ type system by stuffing it into a template. This was originally developed in VC++ with no extensions enabled. No RTTI is required, no headers are required other than for the test app, which needs tchar
and iostream
.
My aim was for simplicity of use. I looked into slots and signals in Boost, and it didn't appeal to me. Mostly because the semantics weren't along what I was seeking. At the time I built this (early 2004), I hadn't known about Dan's Fast Delegate, otherwise I might have used that instead and still might. What I needed was to be able to declare a handler type based on a function signature and use it as a functor. I didn't worry too much about whether it held the hand of the client developer either, it expects a responsible usage. It does, however, limit its usage to common interfaces such as the CCmdTarget
used by MFC to map to Thread and Window classes. I use IDispatchTarget
as my common base. If you need your design to support different base types within the same handling mechanism, then I'd suggest one of the other articles mentioned previously.
It's called a handler because that's the semantics I was using at the time. I had done a little C#, attending the Guerrilla.NET course at DevelopMentor in 2001, but really kept working in C++, so .NET just had enough chance to seep into my subconscious. I didn't really think about .NET or C# and the delegate/event relationship until recently when I returned to the business sector and realized what I was mimicking. It was a little painful going back and cleaning up this code for the article, as now I'm spoiled by C# as well as the .NET framework. I really appreciate the effort and the resulting design that went into both the language and the framework. That being said, I am still a lover of C++. I cut my teeth on it, and really when you need that performance gain, there's no substitute when combining speed with semantics.
Using the code
I built this so that member function pointers could be directly assigned to a functor for up to seven parameters and wanted to support returns. So, I took the approach of having the template parameters default and use an enum
to state the number of parameters used. This is somewhat of a brute force approach, but eliminates the need for a different template for each function signature differing by the number of parameters.
Assignment follows the standard member function pointer syntax. And, usage only requires initializing the handler with the parent's this
pointer and assigning the member function of choice directly to the handler.
Usage would look something like:
#include<tchar.h>
#include <iostream>
using namespace std;
class MHTester;
typedef TMappedHandler<MHTester, e1Params, YES,
bool, int> TestHandler;
class MHTester
{
TestHandler m_handler;
public:
MHTester() : m_handler(this)
{
m_handler = &MHTester::TestMethod;
}
TestHandler &GetHandler()
{
return m_handler;
}
bool TestMethod(int testValue)
{
cout << "\t" << testValue
<< "\tis the number being tested!\n\n";
return true;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
MHTester tester;
TestHandler handler = tester.GetHandler();
handler(5);
handler(6);
return 0;
}
As we can see, the usage is simple enough. We assign and call. As long as we are mindful of threaded conditions, we shouldn't have any trouble using it. So, what's under the hood then? Well, let's look.
namespace def
{
class Default { public: Default() { } };
class emptyVoidReturnSigAmibiguityFix { };
};
typedef def::Default& TDEF_DATA;
typedef def::emptyVoidReturnSigAmibiguityFix* HiddenParam;
#define HiddenParamVal (def::emptyVoidReturnSigAmibiguityFix*)0
typedef enum { e0Params = 0, e1Params, e2Params, e3Params, e4Params,
e5Params, e6Params, e7Params } eNumberOfParams;
typedef enum { NO = 0, YES = 1 } eReturn;
#define SIGNATURE_ALGO(P, R)\
((P == e0Params && R == NO) ? 0 : (1+((1+R)*P)))
Well, first of all, we have a few defaults to set up. Most of these were discovered while compiling. Default values for the template parameters were needed that wouldn't conflict with the user defined types. Another thing that couldn't conflict that was needed was the one to handle the ambiguity that resulted from having two call operators differing only by return type. Since I was developing on VC 6, I didn't use any of the more enhanced abilities of templates. So, this was the approach taken. Then, some enumerations were defined for determining the number of params and a return value. The number of params is used as a hint for determining the function pointer to use by the macro SIGNATURE_ALGO
. It's also used within the enhanced version which is meant to take a stream of data, and uses an internal cracker to parse the parameters and then calls the appropriate handler. All of this is intended to be used by remote procedures and scripting within the framework.
The hit comes when mapping a void
return function. I tend to inherit a class from the typedef'd template definition, and then obscure the dependence on the hidden parameter by supplying it through its own call operator. Not really the most elegant, and may get refactored.
class Handler : TMappedHandler<MappedParent>
{
Handler() : HandlerBase(0) {}
public:
Handler(const Handler &rhs) : HandlerBase(rhs)
{
}
Handler &operator=(const Handler &rhs)
{
if(&rhs == this)
return *this;
HandlerBase::operator=(rhs);
return *this;
}
Handler(MappedParent *pParent) : HandlerBase(pParent)
{
HandlerBase::operator=(&MappedParent::MethodName);
}
void operator()()
{
HandlerBase::operator()(HiddenParamVal);
}
};
This allows us to obscure the details, as well as allows callers to not need to know about the hidden parameter requirement for void
return methods.
Now, for the meat of the implementation, the template itself:
template<typename tMapParent = def::Default,
eNumberOfParams ePARAMS = e0Params, eReturn eRET = NO,
typename tResult = TDEF_DATA, typename tP1 = TDEF_DATA,
typename tP2 = TDEF_DATA, typename tP3 = TDEF_DATA,
typename tP4 = TDEF_DATA, typename tP5 = TDEF_DATA,
typename tP6 = TDEF_DATA, typename tP7 = TDEF_DATA>
class TMappedHandler
{
TMappedHandler() : m_pThis(0) { }
tMapParent *m_pThis;
protected:
typedef TMappedHandler<tMapParent, ePARAMS, eRET, tResult, tP1,
tP2, tP3, tP4, tP5, tP6, tP7> HandlerBase;
public:
TMappedHandler(const HandlerBase &rhs) { *this = rhs; }
TMappedHandler(tMapParent *pThis) : m_pThis(pThis) { }
typedef void(tMapParent::*FX_pDef)();
typedef tResult(tMapParent::*FX_p0PWRet)();
typedef void(tMapParent::*FX_p1PWORet)(tP1);
typedef tResult(tMapParent::*FX_p1PWRet)(tP1);
typedef void(tMapParent::*FX_p2PWORet)(tP1, tP2);
typedef tResult(tMapParent::*FX_p2PWRet)(tP1, tP2);
typedef void(tMapParent::*FX_p3PWORet)(tP1, tP2, tP3);
typedef tResult(tMapParent::*FX_p3PWRet)(tP1, tP2, tP3);
typedef void(tMapParent::*FX_p4PWORet)(tP1, tP2, tP3, tP4);
typedef tResult(tMapParent::*FX_p4PWRet)(tP1, tP2, tP3, tP4);
typedef void(tMapParent::*FX_p5PWORet)(tP1, tP2, tP3, tP4, tP5);
typedef tResult(tMapParent::*FX_p5PWRet)(tP1, tP2, tP3, tP4, tP5);
typedef void(tMapParent::*FX_p6PWORet)(tP1, tP2, tP3, tP4, tP5, tP6);
typedef tResult(tMapParent::*FX_p6PWRet)(tP1, tP2, tP3, tP4, tP5, tP6);
typedef void(tMapParent::*FX_p7PWORet)(tP1, tP2, tP3, tP4, tP5, tP6, tP7);
typedef tResult(tMapParent::*FX_p7PWRet)(tP1, tP2, tP3, tP4, tP5, tP6, tP7);
protected:
union MMF
{
FX_pDef pfn;
FX_p0PWRet pfn0;
FX_p1PWORet pfnV1;
FX_p1PWRet pfn1;
FX_p2PWORet pfnV2;
FX_p2PWRet pfn2;
FX_p3PWORet pfnV3;
FX_p3PWRet pfn3;
FX_p4PWORet pfnV4;
FX_p4PWRet pfn4;
FX_p5PWORet pfnV5;
FX_p5PWRet pfn5;
FX_p6PWORet pfnV6;
FX_p6PWRet pfn6;
FX_p7PWORet pfnV7;
FX_p7PWRet pfn7;
};
FX_pDef m_pfn;
public:
HandlerBase& operator=(const HandlerBase &rhs)
{
if(&rhs == this)
return *this;
m_pThis = rhs.m_pThis;
m_pfn = rhs.m_pfn;
return *this;
}
FX_pDef operator=(FX_pDef pfn)
{
m_pfn = pfn;
return pfn;
}
FX_p0PWRet operator=(FX_p0PWRet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p1PWORet operator=(FX_p1PWORet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p1PWRet operator=(FX_p1PWRet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p2PWORet operator=(FX_p2PWORet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p2PWRet operator=(FX_p2PWRet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p3PWORet operator=(FX_p3PWORet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p3PWRet operator=(FX_p3PWRet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p4PWORet operator=(FX_p4PWORet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p4PWRet operator=(FX_p4PWRet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p5PWORet operator=(FX_p5PWORet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p5PWRet operator=(FX_p5PWRet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p6PWORet operator=(FX_p6PWORet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p6PWRet operator=(FX_p6PWRet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p7PWORet operator=(FX_p7PWORet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
FX_p7PWRet operator=(FX_p7PWRet pfn)
{
m_pfn = (FX_pDef)pfn;
return pfn;
}
typedef HiddenParam HP;
#define MMF_CALL(N) MMFmmf;mmf.pfn=m_pfn;(m_pThis->*mmf.pfnV##N)
#define return_MMF_CALL(N) MMFmmf;mmf.pfn=m_pfn;
return(m_pThis->*mmf.pfn##N)
void operator()(HP)
{
(m_pThis->*m_pfn)();
}
tResult operator()()
{
return_MMF_CALL(0)();
}
void operator()(tP1 p1,HP)
{
MMF_CALL(1)(p1);
}
tResult operator()(tP1 p1)
{
return_MMF_CALL(1)(p1);
}
void operator()(tP1 p1,tP2 p2,HP)
{
MMF_CALL(2)(p1,p2);
}
tResult operator()(tP1 p1,tP2 p2)
{
return_MMF_CALL(2)(p1,p2);
}
void operator()(tP1 p1,tP2 p2,tP3 p3,HP)
{
MMF_CALL(3)(p1,p2,p3);
}
tResult operator()(tP1 p1,tP2 p2,tP3 p3)
{
return_MMF_CALL(3)(p1,p2,p3);
}
void operator()(tP1 p1,tP2 p2,tP3 p3,tP4 p4,HP)
{
MMF_CALL(4)(p1,p2,p3,p4);
}
tResult operator()(tP1 p1,tP2 p2,tP3 p3,tP4 p4)
{
return_MMF_CALL(4)(p1,p2,p3,p4);
}
void operator()(tP1 p1,tP2 p2,tP3 p3,tP4 p4,tP5 p5,HP)
{
MMF_CALL(5)(p1,p2,p3,p4,p5);
}
tResult operator()(tP1 p1,tP2 p2,tP3 p3,tP4 p4,tP5 p5)
{
return_MMF_CALL(5)(p1,p2,p3,p4,p5);
}
void operator()(tP1 p1,tP2 p2,tP3 p3,tP4 p4,tP5 p5,tP6 p6,HP)
{
MMF_CALL(6)(p1,p2,p3,p4,p5,p6);
}
tResult operator()(tP1 p1,tP2 p2,tP3 p3,tP4 p4,tP5 p5,tP6 p6)
{
return_MMF_CALL(6)(p1,p2,p3,p4,p5,p6);
}
void operator()(tP1 p1,tP2 p2,tP3 p3,tP4 p4,tP5 p5,tP6 p6,tP7 p7,HP)
{
MMF_CALL(7)(p1,p2,p3,p4,p5,p6,p7);
}
tResult operator()(tP1 p1,tP2 p2,tP3 p3,tP4 p4,tP5 p5,tP6 p6,tP7 p7)
{
return_MMF_CALL(7)(p1,p2,p3,p4,p5,p6,p7);
}
};
And that's that. Not too complicated, but uses the hack of taking advantage of the union method that mimicks MFC but attempts to reduce the subversion of the underlying types. I didn't have a need to compare handlers, so those operators weren't implemented. But a handy event system can be derived from this initial handler as long as a common base interface is acceptable to your design.
I've only tested it against MSVC 6, MSVC 2003, and GCC 4.1.1, so it might not work for all compilers. Again, my need was just for building apps for Linux and Windows.
History