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

.NET like Delegates in Unmanaged C++

By , 26 Sep 2002
 

Introduction

Users of C#, VB.NET and MC++ have a nice feature available :- delegates. The C++ language does not support this construct. But fortunately, there is a way to implement rudimentary delegates using templates and some clever tricks borrowed from the boost library.

I assume that you have a solid C++ background. In this article I will solve the delegate problem using member function pointers and templates. You may want to read up on these topics before you read any further.

What is a delegate?

For those who have not yet been acquainted with .NET-languages, here's a short explanation.

Put simply, delegates are objects which allows calling methods on objects. Big deal? It is a big deal, since these objects masquerade as free functions with no coupling whatsoever to a specific object. As the name implies, it delegates method calls to a target object.

A first C++ stab at delegates

Since it is possible to take the address of a member function, and apply that member function on any object of the class which defined the member function, it is logical that one should be able make a delegate construct. One way would be to store the address of an object alongside with one of its member functions. The storage could be an object which overloads operator(). The type signature (return type and argument types) of operator() should match the type signature of the member function which we are use for delegation. A very non-dynamic version could be:

struct delegate {
    type*   obj;                    
        // The object which we delegate the call to

    int (type::* method)(int);      
        // Method belonging to type, taking an int 
        // and returning an int 

    delegate(type* obj_, 
        int (type::* method)(int)) : obj(obj_), 
        method(method_) { }

    int operator()(int x) {         
            // See how this operator() matches 
            // the method type signature

        return (obj->*method)(x);   
            // Make the call
    }
};

The above solution is not dynamic in any way. It can only deal with objects of type type, methods taking an int and returning an int. This forces us to either write a new delegate type for each object type/method combination we wish to delegate for, or use object polymorphism where all classes derive from type - and be satisfied with only being able to delegate virtual methods defined in type which matches the int/int type signature! Clearly, this is not a good solution.

A second C++ stab at delegates

Obviously we need to parameterize the object type, parameter type and return type. The only way to do that in C++ is to use templates. A second attempt may look like this:

template <typename Class, typename T1, typename Result>
struct delegate {
    typedef Result (Class::* MethodType)(T1);
    Class*          obj;
    MethodType      method;

    delegate(Class* obj_, 
        MethodType method_) : obj(obj_), method(method_) { }

    Result operator()(T1 v1) {
        return (obj->*method)(v1);
    }
};

Much better! Now we can delegate any object and any method with one parameter in that object. This is a clear improvement over the previous implementation.

Unfortunately, it is not possible to write the delegate so that it can handle any number of arguments. To solve this problem for methods taking two parameters, one has to write a new delegate which handles two parameters. To solve the problem for methods taking three parameters, one has to write a new delegate which handles three parameters - and so on. This is however not a big problem. If you need to cover all your methods, you will most likely not need more than ten such delegate templates. How many of your methods have more than ten parameters? If they do, are you sure they should have more than ten? Also, you'd only need to write these ten delegate once - the sweet power of templates.

However, a small problem, besides the parameter problem, still remains. When this delegate template is instantiated, the resulting delegate type will only be able to handle delegations for the class you supplied as template parameter. The delegate<A, int, int> type is different from delegate<B, int, int>. They are similar in that they delegate method calls taking an int and returning an int. They are dissimilar in that they do not delegate for methods of the same class. .NET delegates ignore this dissimilarity, and so should we!

A third and final stab at delegates

To remove this type dissimilarity, it is obvious that we need to remove the class type as a template parameter. This is best accomplished by using object polymorphism and template constructors. This is a technique which I've borrowed from the boost template library. More specifically, I borrowed it from the implementation of the any class in that library.

Since I'm not a native English writer, I will not attempt to describe the final code with words. I could try but I think I'd just make it more complex than it is. Put simply, I use polymorphism and a templated constructor to gain an extra level of indirection so that I can "peel" away the class information from the delegate. Here's the code:

// The polymorphic base
template <typename T1, typename Result>
struct delegate_base { // Ref counting added 2002-09-22
    int ref_count; // delegate_base's are refcounted

    delegate_base() : ref_count(0) { }

    void addref() {
        ++ref_count;
    }

    void release() {
        if(--ref_count < 0)
            delete this;
    }

    virtual ~delegate_base() { } // Added 2002-09-22
    virtual Result operator()(T1 v1) = 0;
};

// The actual implementation of the delegate
template <typename Class, typename T1, typename Result>
struct delegate_impl : public delegate_base<T1, Result> {
    typedef Result (Class::* MethodType)(T1);
    Class*          obj;
    MethodType      method;

    delegate_impl(Class* obj_, MethodType method_) : 
            obj(obj_), method(method_) { }

    Result operator()(T1 v1) {
        return (obj->*method)(v1);
    }
};

template <typename T1, typename Result>
struct delegate {
    // Notice the type: delegate_base<T1, 
    // Result> - no Class in sight!
    delegate_base<T1, Result>*        pDelegateImpl;

    // The templated constructor - The presence of Class 
    // does not "pollute" the class itself
    template <typename Class, typename T1, typename Result>
    delegate(Class* obj, Result (Class::* method)(T1)) 
        : pDelegateImpl(new delegate_impl<Class, 
        T1, Result>(obj, method)) { 
            pDelegateImpl->addref(); // Added 2002-09-22
        }

        // Copy constructor and assignment operator 
        // added 2002-09-27
        delegate(const delegate<T1, 
            Result>& other) {
            pDelegateImpl = other.pDelegateImpl;
            pDelegateImpl->addref();
        }

        delegate<T1, Result>& operator=(const delegate<T1, 
            Result>& other) {
            pDelegateImpl->release();
            pDelegateImpl = other.pDelegateImpl;
            pDelegateImpl->addref();
            return *this;
        }

        ~delegate() { pDelegateImpl->release(); 
            } // Added & modified 2002-09-22

        // Forward the delegate to the delegate implementation
        Result operator()(T1 v1) {
            return (*pDelegateImpl)(v1);
        }
};

There, .NET delegate requirements satisfied! For information on how to actually use the delegates, see the demo source code available for download at the top of this article.

Why?

Because I think delegates can be quite powerful, and I for one like to have powerful tools in the toolbox. They might be useful some day!

Updates

  • 2002-09-22: Removed a memory leak
  • 2002-09-22: Memory leak fixed introduced a more serious problem: dangling pointers. Solved using reference counting a'la COM.
  • 2002-09-27: Added copy constructor and assignment operator

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

Jörgen Sigvardsson
Software Developer (Senior)
Sweden Sweden
Member
I make software.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralRe: no heap allocation, object method and static class methodmemberJörgen Sigvardsson5 Mar '03 - 8:12 
It's hard to follow it, since of the original code much is gone. Mainly <'s being interpreted by the browser. But from what I can tell it's a nice addition. I'll keep this in mind until the next time I get to do some work on the delegates. I'm planning on using them in a project of mine, but I have not yet begun that coding part. When I get there, I'll revise the code and add your proposed changes (and update the article of course Smile | :) )
 

boutblock wrote:
Is there anyone here interested for adding multi-param to this already tricky stuff ?
 
What exactly do you mean by multi-param? Variable arguments being packed into an object[] array? Smile | :) That doesn't really fit into C++, mainly because there is no object nor any multi-param language construct. Well, there is, but it's the good old C way of doing things .... I don't see how you could forward such parameters without making assumptions on how the compiler generates code for function calls (evil! Wink | ;) ).
 
--
Shine, enlighten me - shine
Shine, awaken me - shine
Shine for all your suffering - shine

GeneralRe: no heap allocation, object method and static class methodmemberboutblock5 Mar '03 - 9:37 
About multi params as object[], of course not Smile | :)
I just meant to insert what Mike Junkins already did to your code ! hehe
 
Now you're talking about weird ideas dealing with the way compilers work, I've got one. I'd like to be able to use those delegates as one can in C#, that is, passing the 'this' pointer as the pointed object implicitly.
 
I tried and failed with a mix of templates and inheritage. Only thing i found as close as possible was to define a new method of my this object to encapsulate the delegate construction (poor result Frown | :-(
 

 

 


GeneralRe: no heap allocation, object method and static class methodmemberJörgen Sigvardsson5 Mar '03 - 9:43 
boutblock wrote:
I'd like to be able to use those delegates as one can in C#, that is, passing the 'this' pointer as the pointed object implicitly.
 
For that you'll have to tweak the compiler I'd say. Smile | :)
 
--
Shine, enlighten me - shine
Shine, awaken me - shine
Shine for all your suffering - shine

GeneralDarnmemberJörgen Sigvardsson26 Sep '02 - 1:27 
I forgot about implementing copy constructors and assignment operator for the delegates Blush | :O . They should pay respect to the reference counting..
 
I'll fix it when I get home.
 
--
Iron Maiden (Harris/Gers) wrote:
The rebel of yesterday, tomorrow's fool
Who are you kidding being that cool?

Generalno heap allocation, multi-parammemberMike Junkin23 Sep '02 - 18:40 
 
Jörgen,
 
Nice article, fun to read about interesting ways to use templates.
 
I downloaded the code and started experimenting with ways to remove the heap allocation. Old style C casts (C++ cast operator) seems to do the trick. I've only tested it by using the example you sent (release and debug builds) however so there might be some bugs lurking...
 
Anyway, here's a new delegate class that consolidates all the behavior into one class. It uses a templated static method in a nested class to 'save' the behavior required to do the proper cast.
 
 
template <typename PARAM_TYPE, typename RESULT_TYPE>
class delegate2
{
	typedef delegate2<PARAM_TYPE,RESULT_TYPE>	SELF;
        // we don't really care about the method signature below
           // except that it's a pointer to a class member function.
           // the compiler should insure it's a big enough pointer 
           // note: compiler options might affect this? 
           // (/vmx or #pragma pointers_to_members) 
	typedef RESULT_TYPE (SELF::*pfMethodType)(PARAM_TYPE);
	typedef RESULT_TYPE (*pfStaticInvokeFunc)(void* pObj, pfMethodType pFunc, PARAM_TYPE param);
 
	template< typename TYPE, typename PARAM_TYPE, typename RESULT_TYPE >
     // this class is used to create a static function that 
        // 'knows' how to cast our generic delegate state information
        // to the right types
	class SaveClassType
	{
		typedef RESULT_TYPE (TYPE::* pfDelegateFunc)(PARAM_TYPE);
	public:
		static RESULT_TYPE Invoke(void* pObj, pfMethodType pFunc, PARAM_TYPE param)
		{
			return (static_cast<TYPE*>(pObj)->*((pfDelegateFunc)pFunc))(param);
		}
	};
public:
 
	template <typename TYPE, typename PARAM_TYPE, typename RESULT_TYPE>
	delegate2(TYPE* pObj, RESULT_TYPE (TYPE::* method)(PARAM_TYPE)):
	m_pObj(pObj),
	m_pFunc((pfMethodType)(method))
	{ 
             // this creates a static method with the proper 
                // casting behavior which we save with a pointer. 
		m_pfInvoke = SaveClassType<TYPE,PARAM_TYPE,RESULT_TYPE>::Invoke;
	}
	
	RESULT_TYPE operator()(PARAM_TYPE param)
	{
		assert(m_pfInvoke);
 
		return (*m_pfInvoke)(m_pObj,m_pFunc,param);
	}
 
private:
	void*			m_pObj;
	pfMethodType		m_pFunc;
	pfStaticInvokeFunc	m_pfInvoke;
};
 
 
It is also possible to create a class that'll do multiple parameters or rather variable parameters. But I'm not sure it's worth it because of the type safety issues it puts into play. It might be possible to code around that as well but I didn't have the time to mess with it...
 
Still here's a way to do it that relies on incomplete template instantiation - the complier won't add the template methods we don't use. That and yet more casting. I only added the possibility for 1 or 2 params, but zero..n could be added.
 
 
 
template< typename RESULT_TYPE >
class delegate_ex
{
	typedef RESULT_TYPE (delegate_ex<RESULT_TYPE>::*pfMethodType)();
 
	typedef RESULT_TYPE (*pfStaticInvokeFunc)(void* pObj, 
                                                  pfMethodType pFunc,
                                                  void* param);
	typedef RESULT_TYPE (*pfStaticInvokeFunc2)(void* pObj,
                                                   pfMethodType pFunc, 
                                                   void* param1,
                                                   void* param2);
 
	template< typename TYPE,
                     typename RESULT_TYPE ,  
                     typename PARAM_TYPE >
	class SaveClassType
	{
	  typedef RESULT_TYPE (TYPE::* pfDelegateFunc)(PARAM_TYPE);
	public:
	   static RESULT_TYPE Invoke(void* pObj, 
                                     pfMethodType pFunc, 
                                     void* param)
	   {
             return (static_cast<TYPE*>(pObj)->*((pfDelegateFunc)pFunc))
             (*static_cast<PARAM_TYPE*>(param));
	   }
	};
 
	template< typename TYPE,
                     typename RESULT_TYPE , 
                     typename PARAM_TYPE, 
                     typename PARAM_TYPE2 >
	class SaveClassType2
	{
		typedef RESULT_TYPE (TYPE::* pfDelegateFunc)
                                            (PARAM_TYPE,PARAM_TYPE2);
	public:
	   static RESULT_TYPE Invoke(void* pObj,
                                     pfMethodType pFunc, 
                                     void* param1,
                                     void* param2)
	   {
	       return (static_cast<TYPE*>(pObj)->*((pfDelegateFunc)pFunc))
              (*static_cast<PARAM_TYPE*>(param1),
               *static_cast<PARAM_TYPE2*>(param2));
	   }
	};
public:
 
	template <typename TYPE, 
                     typename RESULT_TYPE,
                     typename PARAM_TYPE>
	delegate_ex(TYPE* pObj, RESULT_TYPE (TYPE::* method)(PARAM_TYPE)):
	m_pObj(pObj),
	m_pFunc((pfMethodType)(method))
	{ 
		m_pfInvoke = SaveClassType<TYPE,
                                              RESULT_TYPE,
                                              PARAM_TYPE>::Invoke;
	}
 
	template <typename TYPE, 
                     typename RESULT_TYPE, 
                     typename PARAM_TYPE,
                     typename PARAM_TYPE2>  
        delegate_ex(TYPE* pObj,
                    RESULT_TYPE (TYPE::* method)(PARAM_TYPE,PARAM_TYPE2)):
	m_pObj(pObj),
	m_pFunc((pfMethodType)(method))
	{ 
		m_pfInvoke2 = SaveClassType2<TYPE,
                                                RESULT_TYPE,
                                                PARAM_TYPE,
                                                PARAM_TYPE2>::Invoke;
	}
	
	template< typename PARAM_TYPE >
	RESULT_TYPE operator()(PARAM_TYPE param)
	{
		assert(m_pfInvoke);
 
		return (*m_pfInvoke)(m_pObj,m_pFunc,&param);
	}
 
	template< typename PARAM_TYPE, typename PARAM_TYPE2 >
	RESULT_TYPE operator()(PARAM_TYPE param1, PARAM_TYPE2 param2)
	{
		assert(m_pfInvoke);
 
		return (*m_pfInvoke2)(m_pObj,m_pFunc,&param1,&param2);
	}
 
private:
	void*          m_pObj;
	pfMethodType   m_pFunc;
 
	union
	{
	      pfStaticInvokeFunc   m_pfInvoke;
	      pfStaticInvokeFunc2  m_pfInvoke2;
	};
};
 
 
Although this class will work it is not very safe. Nothing prevents, for example:
 
 
struct F : public A
{
	void func(int x)
	{
		...
	}
 
	void func2(int x, int y)
	{
		...
	}
};
 
	typedef delegate_ex<void>  delegate_t;
	std::list<delegate_t>      delegate_list;
 
	delegate_list.push_back(delegate_t(&e,E::func2));
	delegate_list.push_back(delegate_t(&f,F::func2));
	delegate_list.push_back(delegate_t(&f,F::func));
 
	int y = 0;
	for(i = delegate_list.begin();i != delegate_list.end();++i,y++)
	{
		(*i)(j++,y);
	}
 
 
Worse things would happen given functions with signatures containing params of different types...
 
Non-variable params example is type safe as the correct casts are enforced.
 
Anyway, it's fun to play around with...
 
Mike
 

 

GeneralRe: no heap allocation, multi-parammemberJörgen Sigvardsson23 Sep '02 - 21:26 
Mike Junkin wrote:
Anyway, here's a new delegate class that consolidates all the behavior into one class. It uses a templated static method in a nested class to 'save' the behavior required to do the proper cast.
 
Interesting solution indeed. It is type safe eventhough void pointers are used, much thanks to the template types.
 
You could optimize the code further by making pfStaticInvokeFunc m_pfInvoke; static. This'll save 4 bytes (depending on platform) per object.
 
I think I'll study this solution further when I get home.
 

Mike Junkin wrote:
It is also possible to create a class that'll do multiple parameters or rather variable parameters. But I'm not sure it's worth it because of the type safety issues it puts into play.
 
Yes, the type system is seriously in danger. Wink | ;) I think it's only logical though that operator()'s with different parameter count should be in different interfaces. The number of parameters are indeed a part of the type signature. If you want to maintain type safety of any kind, then multiple interfaces is a must I'm afraid.
 
Woe to you, Oh Earth and Sea,for the Devil sends the beast with wrath, because he knows the time is short...
 
Let him who hath understanding reckon the number of the beast for it is a human number, it's number is Six hundred and sixty six

GeneralRe: no heap allocation, multi-parammemberMike Junkin24 Sep '02 - 5:57 
Jörgen Sigvardsson wrote:
You could optimize the code further by making pfStaticInvokeFunc m_pfInvoke; static. This'll save 4 bytes (depending on platform) per object
 
I don't think you can make the pointer static. If you do then every delegate, with the same combination of return/arguments, will cast itself as whatever the last created delegate points too. In your example everything would cast itself as a 'D' object.
 
It's that dynamic pointer that lets the delegates vary their callback class on a dynamic, per instance, basis.
 

Jörgen Sigvardsson wrote:
If you want to maintain type safety of any kind, then multiple interfaces is a must I'm afraid.
 
I agree with you. I included the multi-param option only to show it could be done. I don't think it's pratical due to the lack of type safety.
 
It's possible RTTI could be used to make it safe and useful. In fact RTTI would probably open up other alternatives to both of the classes.
 
Mike

GeneralRe: no heap allocation, multi-parammemberJörgen Sigvardsson24 Sep '02 - 9:48 
Mike Junkin wrote:
I don't think you can make the pointer static. If you do then every delegate, with the same combination of return/arguments, will cast itself as whatever the last created delegate points too. In your example everything would cast itself as a 'D' object.
 
Doh! You're right. I forgot about the void pointer.
 

Mike Junkin wrote:
It's possible RTTI could be used to make it safe and useful. In fact RTTI would probably open up other alternatives to both of the classes.
 
Can you extract RTTI information about method/function signatures? If so, then yes, it would be possible. Allthough, the risk is high that one ends up with the "Java syndrome". All trapped "errors" would have to be guarded by throws - and this doesn't make the implementation exception neutral. Frown | :(
 
Woe to you, Oh Earth and Sea,for the Devil sends the beast with wrath, because he knows the time is short...
 
Let him who hath understanding reckon the number of the beast for it is a human number, it's number is Six hundred and sixty six

GeneralRe: Isn't this just a pointer to a member function?memberTodd Smith21 Sep '02 - 16:41 
It's more than just a pointer. It's an object that contains a pointer to a class and a member function pointer. You can't call the member function of a class without an instance of an object. It's the same as a functor in STL.
 
Todd Smith
GeneralRe: Isn't this just a pointer to a member function?memberJörgen Sigvardsson22 Sep '02 - 7:52 
Russ Freeman wrote:
It's jolly difficult to have a pointer to a member function without 'this'
 
Actually no it isn't. The address of member functions are stored together with the class information - determined at compile time1. So it is possible to separate 'this' and methods. If you look under the hood of C++, you'll see that 'this' is merely an extra parameter in each member call. VC++ even has a call type for this - __thiscall.
 
1 With the exception of virtual member functions. These are stored in the virtual pointer table, also known as vtable. I'm not sure how delegates work on virtual methods, but I'm going to find out right now! Smile | :)
 
The Romulans are... beyond arrogant.
202.The justification of profit is profit.

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 27 Sep 2002
Article Copyright 2002 by Jörgen Sigvardsson
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid