.NET like Delegates in Unmanaged C++






3.72/5 (13 votes)
Sep 21, 2002
4 min read

132310

470
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