5,699,997 members and growing! (12,724 online)
Email Password   helpLost your password?
Languages » C / C++ Language » General     Intermediate

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

By Robert Umbehant

A simple C++ delegates class for the special case of using an arbitrator class for function parameters.
VC7, VC7.1, VC8.0, C++, Windows, Visual Studio, Dev

Posted: 24 Apr 2007
Updated: 25 Sep 2007
Views: 7,518
Bookmarked: 6 times
Announcements
Loading...



Search    
Advanced Search
Sitemap
6 votes for this Article.
Popularity: 1.56 Rating: 2.00 out of 5
3 votes, 50.0%
1
1 vote, 16.7%
2
0 votes, 0.0%
3
0 votes, 0.0%
4
2 votes, 33.3%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

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 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 comming 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. The _variant_t simply holds and 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

    "Member Function Pointers and the Fastest Possible C++ Delegates" by Don Clugston
    "The Impossibly Fast C++ Delegates" by Sergey Ryazanov
    "boost::bind functions" by Jaakko Järvi

    History

    License

    This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

    A list of licenses authors might use can be found here

    About the Author

    Robert Umbehant



    Occupation: Web Developer
    Location: United States United States

    Other popular C / C++ Language articles:

    Article Top
    Sign Up to vote for this article
    You must Sign In to use this message board.
    FAQ FAQ Noise ToleranceSearch Search Messages 
     Layout  Per page   
     Msgs 1 to 5 of 5 (Total in Forum: 5) (Refresh)FirstPrevNext
    GeneralRegarding your CArbitrator classmemberWong Shao Voon16:45 24 Apr '07  
    GeneralRe: Regarding your CArbitrator classmemberRobert Umbehant0:42 25 Apr '07  
    GeneralRe: Regarding your CArbitrator classmemberWong Shao Voon17:06 25 Apr '07  
    GeneralRe: Regarding your CArbitrator classmemberRobert Umbehant6:03 26 Apr '07  
    GeneralRe: Regarding your CArbitrator classmemberRobert Umbehant9:06 26 Apr '07  

    General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    PermaLink | Privacy | Terms of Use
    Last Updated: 25 Sep 2007
    Editor:
    Copyright 2007 by Robert Umbehant
    Everything else Copyright © CodeProject, 1999-2008
    Web15 | Advertise on the Code Project