Introduction
Many kudos to Jaakko Järvi, Don Clugston, Sergey Ryazanov, and others that have created really nice delegate libraries. I have spent many hours trying to decipher their implementations.
A recent project of mine was to create a network de-muxing framework in which new functionality could be readily added and invoked from the remote client. I thought delegates would be great here, and I realized that for the special case of invoking delegates where a parameter arbitrator class would be used, a slightly different method was needed than is available in many existing libraries.
In this article, I will explain this method. This is a specific case type delegate library, so it is not as general purpose as the other alternatives.
*Note: If you want to compile this on VC6, you will have to expand the macros.
Application Goals
We are going to create a delegate class that...
- Is as simple and easy to follow as possible
- Does not use dynamic memory allocation
- Is pretty fast
- Is Standard C++
- Will require the use of an arbitrator class to pass parameters
The arbitrator class
What is an arbitrator class? An arbitrator class will hold the value of the parameter and convert to the correct type on the fly. Often, when data is coming from a serialized source such as a file or network connection, it is in an abstract form. To use this data as a parameter to a function, it must be converted to the proper type. The arbitrator class will handle this automatically. It will be up to the originator to ensure that the cast parameters actually make sense to the function.
This class is crafted to handle the situation of using abstract data as function parameters. It is also not necessary to know the number of parameters that a function requires, as this can be handled as a runtime error. This makes this delegate ideal for closing RPC calls.
Just as an example, we will use the _variant_t wrapper class from Microsoft as the Arbitrator. _variant_t simply holds an abstracted representation of a variable, and is able to cast to and from the concrete form. The TArbDelegate class I present here is really only appropriate if the parameters you intend to pass to the delegated functions are already in an abstract form.
How to use
Our delegates will be wrapped in a class named TArbDelegate. Before we jump into how the delegate class works, let's take a look at how it will be used.
typedef adel::TArbDelegate< _variant_t > CDelegate;
class CMyClass
{
public:
int Add2( int a, int b ) { return a + b; }
float Add3( float a, float b, float c ) { return a + b + c; }
void Trace( _variant_t msg ) { adTRACE0( (_bstr_t )msg ); }
_variant_t GetString() { return _T( "Some String\n\n" ); }
};
void test_delegates()
{
CMyClass mc;
CDelegate d;
d.Register( &mc, &CMyClass::Add2 );
int x = d( 2, 3 );
CDelegate d2( &mc, &CMyClass::Add3 );
float y = d2( 1.5, 2.5, 3.5 );
CDelegate d3( &mc, &CMyClass::Trace );
d3( _T( "\nHello World!\n\n" ) );
CDelegate d4( &mc, &CMyClass::GetString );
adTRACE0( (_bstr_t)d4() );
return;
}
You can see the use here is much like the use of a standard delegate. The only real difference is the use of the arbitrator for passing parameters. Notice also, that you do not need to specify the function signature.
How does it work?
First, let's look at a very simple version of the delegate class with all the macros and templates removed. This should make the basic idea much easier to see. This class will only handle functions returning a non-void value and taking two parameters. The actual class is a bit different since it handles multiple parameters and void return types.
Follow along with the comments, and let's let the code speak for itself.
template < typename T_ARB >
class TDelegate
{
public:
typedef T_ARB(*t_Thunk)( void*, void*, T_ARB, T_ARB );
public:
TDelegate()
{ m_pClass = m_pFunction = m_pThunk = 0;
}
template < typename T_RET, typename T_P1, typename T_P2, typename T_CLASS >
void Register( T_CLASS *x_pC, T_RET(T_CLASS::*x_pF)( T_P1, T_P2 ) )
{
_Register< T_RET, T_P1, T_P2 >( x_pC, x_pF );
}
T_ARB Call( T_ARB p1, T_ARB p2 )
{
return m_pThunk( m_pClass, m_pFunction, p1, p2 );
}
protected:
template < typename T_RET, typename T_P1, typename T_P2,
typename T_CLASS, typename T_FUNCTION >
void _Register( T_CLASS *x_pC, T_FUNCTION x_pF )
{
m_pClass = x_pC;
m_pFunction = *(void**)&x_pF;
m_pThunk = (t_Thunk)&Thunk< T_RET, T_P1, T_P2, T_CLASS, T_FUNCTION >;
}
template < typename T_RET, typename T_P1, typename T_P2,
typename T_CLASS, typename T_FUNCTION >
static T_ARB Thunk( void* x_pC, void* x_pF, T_ARB p1, T_ARB p2 )
{
return ( ( (T_CLASS*)x_pC )->*( *( (T_FUNCTION*)&x_pF ) ) )( p1, p2 );
}
private:
void *m_pClass;
void *m_pFunction;
t_Thunk m_pThunk;
};
Invoking the class member function then, is just a matter of getting the pointer to the thunk, and letting it do the translation.
If you like, you can copy this class into your debugger, use it to wrap the CMyClass::Add2() function defined above and watch it in action.
The real deal
Here is the actual TDelegate code, all of it:
#define TARB_PARAM_A_0
#define TARB_PARAM_B_0
#define TARB_PARAM_C_0
#define TARB_PARAM_D_0
#define TARB_PARAM_E_0
#define TARB_PARAM_F_0
#define TARB_PARAM_G_0
#define TARB_PARAM_H_0
#define TARB_PARAM_I_0
#define TARB_PARAM_A_1 typename T_P1,
#define TARB_PARAM_B_1 T_P1
#define TARB_PARAM_C_1 , T_P1
#define TARB_PARAM_D_1 T_P1,
#define TARB_PARAM_E_1 T_ARB p1
#define TARB_PARAM_F_1 , T_ARB p1
#define TARB_PARAM_G_1 , p1
#define TARB_PARAM_H_1 p1
#define TARB_PARAM_I_1 , T_ARB
#define TARB_PARAM_A_2 typename T_P1, typename T_P2,
#define TARB_PARAM_B_2 T_P1, T_P2
#define TARB_PARAM_C_2 , T_P1, T_P2
#define TARB_PARAM_D_2 T_P1, T_P2,
#define TARB_PARAM_E_2 T_ARB p1, T_ARB p2
#define TARB_PARAM_F_2 , T_ARB p1, T_ARB p2
#define TARB_PARAM_G_2 , p1, p2
#define TARB_PARAM_H_2 p1, p2
#define TARB_PARAM_I_2 , T_ARB, T_ARB
#define TARB_PARAM_A_3 typename T_P1, typename T_P2, typename T_P3,
#define TARB_PARAM_B_3 T_P1, T_P2, T_P3
#define TARB_PARAM_C_3 , T_P1, T_P2, T_P3
#define TARB_PARAM_D_3 T_P1, T_P2, T_P3,
#define TARB_PARAM_E_3 T_ARB p1, T_ARB p2, T_ARB p3
#define TARB_PARAM_F_3 , T_ARB p1, T_ARB p2, T_ARB p3
#define TARB_PARAM_G_3 , p1, p2, p3
#define TARB_PARAM_H_3 p1, p2, p3
#define TARB_PARAM_I_3 , T_ARB, T_ARB, T_ARB
#define TARB_PARAM_A_4 typename T_P1, typename T_P2, typename T_P3, typename T_P4,
#define TARB_PARAM_B_4 T_P1, T_P2, T_P3, T_P4
#define TARB_PARAM_C_4 , T_P1, T_P2, T_P3, T_P4
#define TARB_PARAM_D_4 T_P1, T_P2, T_P3, T_P4,
#define TARB_PARAM_E_4 T_ARB p1, T_ARB p2, T_ARB p3, T_ARB p4
#define TARB_PARAM_F_4 , T_ARB p1, T_ARB p2, T_ARB p3, T_ARB p4
#define TARB_PARAM_G_4 , p1, p2, p3, p4
#define TARB_PARAM_H_4 p1, p2, p3, p4
#define TARB_PARAM_I_4 , T_ARB, T_ARB, T_ARB, T_ARB
#define TARB_PARAM_A_5 typename T_P1, typename T_P2,
typename T_P3, typename T_P4, typename T_P5,
#define TARB_PARAM_B_5 T_P1, T_P2, T_P3, T_P4, T_P5
#define TARB_PARAM_C_5 , T_P1, T_P2, T_P3, T_P4, T_P5
#define TARB_PARAM_D_5 T_P1, T_P2, T_P3, T_P4, T_P5,
#define TARB_PARAM_E_5 T_ARB p1, T_ARB p2, T_ARB p3, T_ARB p4, T_ARB p5
#define TARB_PARAM_F_5 , T_ARB p1, T_ARB p2, T_ARB p3, T_ARB p4, T_ARB p5
#define TARB_PARAM_G_5 , p1, p2, p3, p4, p5
#define TARB_PARAM_H_5 p1, p2, p3, p4, p5
#define TARB_PARAM_I_5 , T_ARB, T_ARB, T_ARB, T_ARB, T_ARB
#define TARB_PARAM_A_6 typename T_P1, typename T_P2, typename T_P3,
typename T_P4, typename T_P5, typename T_P6,
#define TARB_PARAM_B_6 T_P1, T_P2, T_P3, T_P4, T_P5, T_P6
#define TARB_PARAM_C_6 , T_P1, T_P2, T_P3, T_P4, T_P5, T_P6
#define TARB_PARAM_D_6 T_P1, T_P2, T_P3, T_P4, T_P5, T_P6,
#define TARB_PARAM_E_6 T_ARB p1, T_ARB p2, T_ARB p3, T_ARB p4, T_ARB p5, T_ARB p6
#define TARB_PARAM_F_6 , T_ARB p1, T_ARB p2, T_ARB p3, T_ARB p4, T_ARB p5, T_ARB p6
#define TARB_PARAM_G_6 , p1, p2, p3, p4, p5, p6
#define TARB_PARAM_H_6 p1, p2, p3, p4, p5, p6
#define TARB_PARAM_I_6 , T_ARB, T_ARB, T_ARB, T_ARB, T_ARB, T_ARB
#define TARB_BODY( n ) \
public: \
template < typename T_RET, TARB_PARAM_A_##n typename T_CLASS > \
TArbDelegate( T_CLASS *x_pC, T_RET(T_CLASS::*x_pF)( TARB_PARAM_B_##n ) ) \
{ _Register< T_RET TARB_PARAM_C_##n >( x_pC, x_pF ); } \
template < typename T_RET, TARB_PARAM_A_##n typename T_CLASS > \
void Register( T_CLASS *x_pC, T_RET(T_CLASS::*x_pF)( TARB_PARAM_B_##n ) ) \
{ _Register< T_RET TARB_PARAM_C_##n >( x_pC, x_pF ); } \
T_ARB operator ()( TARB_PARAM_E_##n ) \
{ typedef T_ARB (*t_Thunk)( void*, void* TARB_PARAM_I_##n ); \
return ((t_Thunk)m_pThunk)( m_pClass, m_pFunction TARB_PARAM_G_##n ); } \
private: \
template < typename T_RET, TARB_PARAM_A_##n typename T_CLASS, typename T_FUNCTION > \
void _Register( T_CLASS *x_pC, T_FUNCTION x_pF ) \
{ m_uParams = n; \
m_pClass = x_pC; \
m_pFunction = *(void**)&x_pF; \
typedef T_ARB (*t_Thunk)( void*, void* TARB_PARAM_I_##n ); \
m_pThunk = (t_Thunk)&SThunk_##n< T_RET >::Thunk< T_RET,
TARB_PARAM_D_##n T_CLASS, T_FUNCTION >; \
} \
template< typename T_RET > struct SThunk_##n \
{ template < typename T_RET, TARB_PARAM_A_##n
typename T_CLASS, typename T_FUNCTION > \
static T_ARB Thunk( void* x_pC, void* x_pF TARB_PARAM_F_##n ) \
{ return ( ( (T_CLASS*)x_pC )->*( *( (T_FUNCTION*)&x_pF ) ) )
( TARB_PARAM_H_##n ); } \
}; \
template <> struct SThunk_##n< void > \
{ template < typename T_RET, TARB_PARAM_A_##n
typename T_CLASS, typename T_FUNCTION > \
static T_ARB Thunk( void* x_pC, void* x_pF TARB_PARAM_F_##n ) \
{ ( ( (T_CLASS*)x_pC )->*( *( (T_FUNCTION*)&x_pF ) ) )
( TARB_PARAM_H_##n ); return 0; } \
};
template < typename T_ARB >
class TArbDelegate
{
public:
TArbDelegate()
{ m_uParams = 0;
m_pClass = m_pFunction = m_pThunk = 0;
}
TArbDelegate( TArbDelegate &x_rD )
{ m_uParams = x_rD.m_uParams;
m_pClass = x_rD.m_pClass;
m_pFunction = x_rD.m_pFunction;
m_pThunk = x_rD.m_pThunk;
}
TArbDelegate& operator = ( TArbDelegate &x_rD )
{ m_uParams = x_rD.m_uParams;
m_pClass = x_rD.m_pClass;
m_pFunction = x_rD.m_pFunction;
m_pThunk = x_rD.m_pThunk;
return *this;
}
TARB_BODY( 0 );
TARB_BODY( 1 );
TARB_BODY( 2 );
TARB_BODY( 3 );
TARB_BODY( 4 );
TARB_BODY( 5 );
TARB_BODY( 6 );
public:
unsigned int GetNumParams()
{ return m_uParams; }
unsigned int IsValid()
{ return 0 != m_pThunk; }
protected:
void* m_pClass;
void* m_pFunction;
void* m_pThunk;
unsigned int m_uParams;
};
As you can see, it's pretty short. Adding the extra parameters and handling the void return type is basically just standard template stuff, so I'm not going to go into details past the code above.
Conclusion
I definitely recommend you look at the other delegate projects referenced below. Real great stuff.
I hope you found this useful.
References