myelin: Implementing delegates in C++

[ Download files from this page: all in one ZIP archive - cpppp.pl, cpptest.cpp, cppauto.pre_h, Makefile ]

We want an equivalent to:

	public delegate void FooDelegate( int a, int b, int c );

Delegates have the following benefits that we would like to keep:

Sun doesn't like them - see here for more details. Basically they found that they didn't work well in a JVM, weren't very memory-efficient, and broke object-orientation. Instead, in Java, you pass around lots of little objects - the interface approach below.

Interface approach:
class ICallback
{
public:
	virtual void Foo( int a, int b, int c );
};

Now derive your class from ICallback (implement the interface) and implement Foo.  The problem here is that you can't implement Foo more than once in the same object, so you can't call back to multiple functions inside the object.

Static function approach:
class CallHere
{
public:
	static void CallOne( int a, int b, int c ) {}
	static void CallTwo( int a, int b, int c ) {}
};

Now we can pass pointers to CallOne and CallTwo, but we have no way of passing 'this' or telling the object at the other end what type of object we are.

Template approach:
template<class T>
	class Delegate : public ICallback
{
private:
	T* _that;
	void (*_func)( int a, int b, int c );

public:
	FakeDelegate( T* that, void (T::*func)( int a, int b, int c ) ) {
		_that = that; _func = func;
	}

	void Call( int a, int b, int c ) {
		_that->*_func( a, b, c );
	}
};

Now the following will make a delegate that will call the function in this object:

Delegate* myDelegate = new Delegate( this, function_name);

Ideally we could make the function definition into a typename too, but I couldn't figure out how to make it work properly.  Here's an example:

template<class T, class U>
	class GenericDelegate
{
private:
	T* _that;
	U* _func;

public:
	GenericDelegate( T* that, U* func ) {
		_that = that;
		_func = func;
	}

	//void Call( int a, int b, int c ) {
		//_that->*_func( a, b, c );
	//}
};

How do we implement the Call method?  We need to automatically translate a call to this:

instance_of_FakeDelegate2->Call( a, b, c )

into a call to this:

_that->*_func( a, b, c )

My solution was to hack up a Perl script to preprocess a header file and generate class definitions, so you write this:

delegate void AutoDelegate( int a, int b, int c );

After running it through the Perl script, you get an interface AutoDelegate (like ICallback above), a class template AutoDelegate_Delegate (like Delegate above) and a function new_AutoDelegate(). Now define your caller like this:

void SetCallback( AutoDelegate* functionToCall );

... and create your delegate object like this:

AutoDelegate* myDelegate = new_AutoDelegate( this, &MyClass::MethodName );

Note that here, MyClass is the surrounding class and MethodName is the method you want to call back.  Don't forget the underscore between new and AutoDelegate.  Just saying "new AutoDelegate" won't work because AutoDelegate is an interface and you can't instantiate an object with pure virtual methods.  new_AutoDelegate is a function to create an object which then gets passed by its AutoDelegate interface.

Note that you need to delete myDelegate once you're done.  To make sure it stays around long enough and gets deleted at the right time, use the magic STL template std::auto_ptr as follows:

Change your SetCallback() function definition to look like this:

void SetCallback( auto_ptr<AutoDelegate> functionToCall );

Now you need to allocate the delegate object like this:

auto_ptr<AutoDelegate> myDelegate( new_AutoDelegate( this, &MyClass::MethodName );

Don't forget to include the header file memory, which defines auto_ptr:

#include <memory>
using namespace std;

Now you can get rid of the 'delete myDelegate' line, because the std::auto_ptr will handle that for you.

Notes

Marcel Mueller proposes how to do this without the Perl script.

    ⇒ myelin | notes | christchurch | net [ video hire ] | software [ dbwrappers | xmlrpc | pycs | pss ] | contact