Click here to Skip to main content
15,881,173 members
Articles / Programming Languages / C++
Article

.NET like Delegates in Unmanaged C++

Rate me:
Please Sign up or sign in to vote.
3.72/5 (13 votes)
26 Sep 20024 min read 129.4K   470   30   46
This article describes a design pattern on how to implement generic delegates as found in .NET, but using ordinary/unmanaged C++

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:

C++
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:

C++
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:

C++
// 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


Written By
Software Developer (Senior)
Sweden Sweden
I make software.

Comments and Discussions

 
QuestionNice one, but... Pin
Marco Bertschi3-Apr-13 9:43
protectorMarco Bertschi3-Apr-13 9:43 
Generalto store a copied pointer is dangerous Pin
semmel7118-Nov-05 5:35
semmel7118-Nov-05 5:35 
GeneralRe: to store a copied pointer is dangerous Pin
pete_b26-Apr-06 22:42
pete_b26-Apr-06 22:42 
GeneralGreat article, good stuff, one thing on memory leaks... Pin
the_grip30-Sep-05 6:43
the_grip30-Sep-05 6:43 
GeneralRe: Great article, good stuff, one thing on memory leaks... Pin
Jörgen Sigvardsson30-Sep-05 11:22
Jörgen Sigvardsson30-Sep-05 11:22 
GeneralMaybe it's my VC6 compiler that coughed. Pin
WREY1-Nov-04 4:28
WREY1-Nov-04 4:28 
GeneralRe: Maybe it's my VC6 compiler that coughed. Pin
Jörgen Sigvardsson1-Nov-04 8:15
Jörgen Sigvardsson1-Nov-04 8:15 
GeneralSWEET! Pin
Rui A. Rebelo19-Nov-03 15:54
Rui A. Rebelo19-Nov-03 15:54 
GeneralRe: SWEET! Pin
Jörgen Sigvardsson19-Nov-03 22:34
Jörgen Sigvardsson19-Nov-03 22:34 
Generalno heap allocation, object method and static class method Pin
Philippe Bouteleux14-Feb-03 3:38
Philippe Bouteleux14-Feb-03 3:38 
Here is my own version of your both delegate classes.
I wanted to be able to declare both static and non-static class methods as regular C# delegates do.
I also needed comparison operators and copy constructor, so i just added them.

Is there anyone here interested for adding multi-param to this already tricky stuff ? Roll eyes | :rolleyes:

Any comments guys ?

<br />
<br />
// this base class is needed to be able to create a pointer to two specialized classes<br />
// Following two sub-classes are used to create static instances that 'knows'        <br />
// how to cast our generic delegate to the right types <br />
// between "pointer to object method" and "pointer to static class method"<br />
template <typename RESULT_TYPE, typename PARAM_TYPE><br />
class delegate_base <br />
{<br />
public:<br />
    typedef delegate_base<RESULT_TYPE, PARAM_TYPE>	SELF;<br />
    typedef RESULT_TYPE (SELF::* pfFuncObject)(PARAM_TYPE);<br />
    typedef RESULT_TYPE (*pfFuncStatic)(PARAM_TYPE);<br />
<br />
    typedef union {<br />
        pfFuncObject pfObject;<br />
        pfFuncStatic pfStatic;<br />
    } uFunc;<br />
<br />
    delegate_base (void) {<br />
        m_obj = NULL;<br />
        m_func.pfObject = NULL;<br />
        m_func.pfStatic = NULL;<br />
    }<br />
<br />
    // used later for comparison operators<br />
    virtual bool Equals (delegate_base<RESULT_TYPE, PARAM_TYPE> &dlg, delegate_base<RESULT_TYPE, PARAM_TYPE> &other) = 0;<br />
<br />
    // generic calling method<br />
    virtual RESULT_TYPE Invoke(delegate_base<RESULT_TYPE, PARAM_TYPE> &dlg, PARAM_TYPE param) = 0;<br />
<br />
    // shared attributs declaration<br />
    void * m_obj;	<br />
    uFunc  m_func;	<br />
};<br />
<br />
// first specific sub-classes designed for "pointer to object method"<br />
template< typename CLASS_TYPE, typename RESULT_TYPE, typename PARAM_TYPE >     <br />
class delegate_object : public delegate_base < RESULT_TYPE, PARAM_TYPE > <br />
{		<br />
    typedef RESULT_TYPE (CLASS_TYPE::* pfDelegateFunc)(PARAM_TYPE);<br />
<br />
public:	<br />
	bool Equals (delegate_base<RESULT_TYPE, PARAM_TYPE> &dlg, delegate_base<RESULT_TYPE, PARAM_TYPE> &other) <br />
    {<br />
        delegate_object<CLASS_TYPE, RESULT_TYPE, PARAM_TYPE> *other_obj = dynamic_cast<delegate_object<CLASS_TYPE, RESULT_TYPE, PARAM_TYPE> *>(&other);<br />
        if (other_obj == NULL)<br />
            return false;<br />
<br />
        if ((dlg.m_obj == other_obj->m_obj) &&<br />
            (dlg.m_func.pfObject == other_obj->m_func.pfObject))<br />
            return true;<br />
<br />
		return false;<br />
	}	<br />
<br />
    RESULT_TYPE Invoke(delegate_base<RESULT_TYPE, PARAM_TYPE> &dlg, PARAM_TYPE param)<br />
    {			<br />
        return (static_cast<CLASS_TYPE*>(dlg.m_obj)->*((pfDelegateFunc)dlg.m_func.pfObject))(param);		<br />
    }	<br />
};<br />
<br />
// second sub-class used to type a delegate as a static class method (aka plain function pointer ;-)<br />
template< typename RESULT_TYPE, typename PARAM_TYPE >     <br />
class delegate_static : public delegate_base < RESULT_TYPE, PARAM_TYPE > {		<br />
public:<br />
<br />
    bool Equals (delegate_base<RESULT_TYPE, PARAM_TYPE> &dlg, delegate_base<RESULT_TYPE, PARAM_TYPE> &other) <br />
    {<br />
        delegate_static<RESULT_TYPE, PARAM_TYPE> *other_static = dynamic_cast<delegate_static<RESULT_TYPE, PARAM_TYPE> *>(&other);<br />
        if (other_static == NULL)<br />
            return false;<br />
<br />
        if (dlg.m_func.pfStatic == other_static->m_func.pfStatic)<br />
            return true;<br />
<br />
		return false;<br />
	}	<br />
<br />
    RESULT_TYPE Invoke(delegate_base<RESULT_TYPE, PARAM_TYPE> &dlg, PARAM_TYPE param)<br />
    {			<br />
        return (dlg.m_func.pfStatic)(param);<br />
    }<br />
};<br />
<br />
// our public delegate interface with two constructors <br />
// used to set the type between the two previous sub-classes<br />
// This public delegate also inherit from the common base class<br />
// to be able to share the definition of its attributs with the sub-classes<br />
// (so we need to declare the virtual functions of the base class)<br />
template <typename RESULT_TYPE, typename PARAM_TYPE><br />
class delegate : public delegate_base < RESULT_TYPE, PARAM_TYPE ><br />
{	<br />
public:	<br />
<br />
    delegate () {<br />
        m_type = NULL;<br />
    }<br />
<br />
    bool isNull() { return m_type == NULL; }<br />
<br />
    // type a delegate as a pointer to object method<br />
    template <typename CLASS_TYPE, typename RESULT_TYPE, typename PARAM_TYPE>	<br />
    delegate(CLASS_TYPE& obj, RESULT_TYPE (CLASS_TYPE::* method)(PARAM_TYPE))<br />
    {           <br />
        static delegate_object<CLASS_TYPE, RESULT_TYPE, PARAM_TYPE> DLG_OBJ;<br />
<br />
        m_obj = &obj;<br />
        m_func.pfObject = (pfFuncObject)(method);<br />
        m_type = &DLG_OBJ;<br />
<br />
    }		<br />
<br />
    // type a delegate as a pointer to static class method<br />
    delegate(RESULT_TYPE (* method)(PARAM_TYPE))<br />
    {           <br />
        static delegate_static<RESULT_TYPE, PARAM_TYPE> DLG_STC;<br />
<br />
        m_obj = NULL;<br />
        m_func.pfStatic = (pfFuncStatic)(method);<br />
        m_type = &DLG_STC;<br />
    }		<br />
<br />
    // copy constructor<br />
    delegate<RESULT_TYPE, PARAM_TYPE>& operator=(const delegate<RESULT_TYPE, PARAM_TYPE>& other) {<br />
        m_obj = other.m_obj;<br />
        m_func = other.m_func;<br />
        m_type = other.m_type;<br />
<br />
		return *this;<br />
	}	<br />
<br />
    // calling operator passes through the virtual function instanciation<br />
    RESULT_TYPE Invoke(delegate_base<RESULT_TYPE, PARAM_TYPE> &dlg, PARAM_TYPE param) {<br />
        return m_type->Invoke(dlg, param);<br />
    }<br />
<br />
    RESULT_TYPE operator()(PARAM_TYPE param) {<br />
        return Invoke (*this, param);<br />
    }<br />
<br />
    // comparison operators pass through the virtual function instanciation<br />
    bool Equals (delegate_base<RESULT_TYPE, PARAM_TYPE> &dlg, delegate_base<RESULT_TYPE, PARAM_TYPE> &other) {<br />
        if (&dlg == &other)<br />
            return true;<br />
<br />
        if (&dlg == NULL || &other == NULL)<br />
            return false;<br />
<br />
        return m_type->Equals (dlg, other);<br />
	}	<br />
<br />
    bool operator==(const delegate<RESULT_TYPE, PARAM_TYPE>& other) {<br />
        return Equals (*this, const_cast<delegate<RESULT_TYPE, PARAM_TYPE> &>(other));<br />
	}	<br />
<br />
    bool operator!=(const delegate<RESULT_TYPE, PARAM_TYPE>& other) {<br />
        return !Equals (*this, const_cast<delegate<RESULT_TYPE, PARAM_TYPE> &>(other));<br />
	}	<br />
<br />
private:<br />
    delegate_base<RESULT_TYPE, PARAM_TYPE> * m_type;<br />
};<br />
<br />

GeneralRe: no heap allocation, object method and static class method Pin
Jörgen Sigvardsson5-Mar-03 8:12
Jörgen Sigvardsson5-Mar-03 8:12 
GeneralRe: no heap allocation, object method and static class method Pin
Philippe Bouteleux5-Mar-03 9:37
Philippe Bouteleux5-Mar-03 9:37 
GeneralRe: no heap allocation, object method and static class method Pin
Jörgen Sigvardsson5-Mar-03 9:43
Jörgen Sigvardsson5-Mar-03 9:43 
GeneralDarn Pin
Jörgen Sigvardsson26-Sep-02 1:27
Jörgen Sigvardsson26-Sep-02 1:27 
Generalno heap allocation, multi-param Pin
Mike Junkin23-Sep-02 18:40
Mike Junkin23-Sep-02 18:40 
GeneralRe: no heap allocation, multi-param Pin
Jörgen Sigvardsson23-Sep-02 21:26
Jörgen Sigvardsson23-Sep-02 21:26 
GeneralRe: no heap allocation, multi-param Pin
Mike Junkin24-Sep-02 5:57
Mike Junkin24-Sep-02 5:57 
GeneralRe: no heap allocation, multi-param Pin
Jörgen Sigvardsson24-Sep-02 9:48
Jörgen Sigvardsson24-Sep-02 9:48 
GeneralRe: Isn't this just a pointer to a member function? Pin
Todd Smith21-Sep-02 16:41
Todd Smith21-Sep-02 16:41 
GeneralRe: Isn't this just a pointer to a member function? Pin
Jörgen Sigvardsson22-Sep-02 7:52
Jörgen Sigvardsson22-Sep-02 7:52 
GeneralRe: Isn't this just a pointer to a member function? Pin
Jörgen Sigvardsson22-Sep-02 8:29
Jörgen Sigvardsson22-Sep-02 8:29 
GeneralRe: Isn't this just a pointer to a member function? Pin
Jörgen Sigvardsson21-Sep-02 22:32
Jörgen Sigvardsson21-Sep-02 22:32 
GeneralRe: Isn't this just a pointer to a member function? Pin
Jörgen Sigvardsson21-Sep-02 23:00
Jörgen Sigvardsson21-Sep-02 23:00 
GeneralRe: Isn't this just a pointer to a member function? Pin
Jörgen Sigvardsson22-Sep-02 1:40
Jörgen Sigvardsson22-Sep-02 1:40 
GeneralRe: Isn't this just a pointer to a member function? Pin
Joao Vaz22-Sep-02 22:41
Joao Vaz22-Sep-02 22:41 

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

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