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;
int (type::* method)(int);
delegate(type* obj_,
int (type::* method)(int)) : obj(obj_),
method(method_) { }
int operator()(int x) {
return (obj->*method)(x);
}
};
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:
template <typename T1, typename Result>
struct delegate_base {
int ref_count;
delegate_base() : ref_count(0) { }
void addref() {
++ref_count;
}
void release() {
if(--ref_count < 0)
delete this;
}
virtual ~delegate_base() { }
virtual Result operator()(T1 v1) = 0;
};
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 {
delegate_base<T1, Result>* pDelegateImpl;
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();
}
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();
}
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