Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

.NET like Delegates in Unmanaged C++

0.00/5 (No votes)
26 Sep 2002 3  
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:

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