Click here to Skip to main content
Click here to Skip to main content

Simple C++ member function delegates class using a parameter arbitrator class

By , 25 Sep 2007
Rate this:
Please Sign up or sign in to vote.

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.

// Define a delegate class that uses our custom arbitrator class
typedef adel::TArbDelegate< _variant_t > CDelegate;

// Class containing functions we will call
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" ); }
};

// Test the delegates
void test_delegates()
{
    CMyClass mc;

    // Call CMyClass::Add1()
    CDelegate d;
    d.Register( &mc, &CMyClass::Add2 );
    int x = d( 2, 3 );

    // Call CMyClass::Add2()
    CDelegate d2( &mc, &CMyClass::Add3 );
    float y = d2( 1.5, 2.5, 3.5 );

    // Call CMyClass::Trace
    CDelegate d3( &mc, &CMyClass::Trace );
    d3( _T( "\nHello World!\n\n" ) );

    // Call CMyClass::GetString
    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.

/// Simple delegate class that wraps a function taking two parameters 
/// and a non void return value
template < typename T_ARB >
    class TDelegate
{
public:

    /// typedef for the thunk function
    typedef T_ARB(*t_Thunk)( void*, void*, T_ARB, T_ARB );

public:

    /// Default constructor
    TDelegate()
    {   m_pClass = m_pFunction = m_pThunk = 0;
    }

    /// This function detects the function parameters
    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 ); 
    }

    /// This function makes the call via the thunk
    T_ARB Call( T_ARB p1, T_ARB p2 )
    {
        return m_pThunk( m_pClass, m_pFunction, p1, p2 ); 
    }

protected:

    /// This function saves the class, function, and thunk pointers
    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 )
    {
        // Save class pointer
        m_pClass = x_pC;

        // Save the function poitner
        m_pFunction = *(void**)&x_pF;

        // Save the thunking function address
        m_pThunk = (t_Thunk)&Thunk< T_RET, T_P1, T_P2, T_CLASS, T_FUNCTION >;
    }

    /// This is the static thunking function, it knows how to make the actual call
    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 )
    {   
        // Make the actual call        
        return ( ( (T_CLASS*)x_pC )->*( *( (T_FUNCTION*)&x_pF ) ) )( p1, p2 );         
    }

private:

    /// Pointer to the class
    void        *m_pClass;

    /// Pointer to the function
    void        *m_pFunction;

    /// Pointer to the thunk
    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

// Delegate function body
#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; }                  \
    };

/// Delegates template that invokes member functions using an arbitration class
template < typename T_ARB >
    class TArbDelegate
{
public:

    /// Constructor
    TArbDelegate()
    {   m_uParams = 0;
        m_pClass = m_pFunction = m_pThunk = 0;        
    }

    /// Copy constructor
    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;
    }

    /// Copy constructor
    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;
    }

    // For each possible number of parameters
    TARB_BODY( 0 );
    TARB_BODY( 1 );
    TARB_BODY( 2 );
    TARB_BODY( 3 );
    TARB_BODY( 4 );
    TARB_BODY( 5 );
    TARB_BODY( 6 );

public:

    /// Returns the number of parameters the function has
    unsigned int GetNumParams()
    {   return m_uParams; }

    /// Returns non-zero if the delegate appears to be set
    unsigned int IsValid()
    {   return 0 != m_pThunk; }

protected:

    /// Class pointer
    void*                   m_pClass;

    /// Function pointer
    void*                   m_pFunction;

    /// Thunk
    void*                   m_pThunk;

    /// Number of params
    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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Robert Umbehant
Software Developer (Senior)
United States United States
No Biography provided

Comments and Discussions

 
GeneralRegarding your CArbitrator class PinmemberWong Shao Voon24-Apr-07 15:45 
GeneralRe: Regarding your CArbitrator class PinmemberRobert Umbehant24-Apr-07 23:42 
Hello Wong,
 
It is the intention of the delegate class to decouple the C++ type checking. The CArbitrator class I provided here is only for demonstration. It is as you point out, and as I labeled it, naive.
 
This delegate class is not intended to be used as you have done here. You need a general case delegate for that. If you imagine this class being used to complete the circuit for a remote procedure call, it is appropriate. The C++ type checking can not infer function parameter types from a socket buffer after all Wink | ;)

GeneralRe: Regarding your CArbitrator class PinmemberWong Shao Voon25-Apr-07 16:06 
GeneralRe: Regarding your CArbitrator class PinmemberRobert Umbehant26-Apr-07 5:03 
GeneralRe: Regarding your CArbitrator class PinmemberRobert Umbehant26-Apr-07 8:06 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 25 Sep 2007
Article Copyright 2007 by Robert Umbehant
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid