So if you're somewhat familiar with C++ you know you can't easily implement a nice generic delegate that works for both static classes and member functions of all classes.  However, with some research and thought, you will realize it's not as hard as you think!

First, I would like to share this article that inspired me and caused me to gain hope in that delegates are possible in a nice flexible way!  Here it is, on: Code Project.

Okay, let's first think about this, we want a delegate to hide away the C++ syntax so we can call any kind of function, pass in certain parameters and get something back.  To make this flexible, it makes sense to have the base delegate class be a template, so you can define what types you want to use.  So here is the base class, which I'll call IDelegate, as in Interface Delegate.

// Interface for a Delegate class, for dispatching a lookup function call.
// R is the return type
// P is the parameter type
template <typename R,typename P>
class IDelegate
{
public:
virtual ~IDelegate(){}

virtual R Invoke(P) = 0;
};

You could also just as easily use an operator overload instead of Invoke:

virtual R operator()(P) = 0;

Now we just need to worry about creating a subclass so we can use these Delegates! So, we'll start with the Static Delegate since it'll be the simplest to create. To create a Static Delegate, we'll need a StaticDelegate class that accepts a static function pointer, and that's all it needs. So we have that below:

// This class is for attaching a Static Function Pointer to an IDelegate
// R is the return type
// P is the parameter type
template <typename R,typename P>
class StaticDelegate<typename R,typename P>
	: public IDelegate<R,P>
{
	typedef R (*funcPtr)(P);

protected:
	funcPtr _funcPtr;

public:
	StaticDelegate(funcPtr functionPointer)
		: _funcPtr( functionPointer )
	{
	}

	R Invoke(P param)
	{
		return _funcPtr(param);
	}
};

For those of you who don't know, this is a Static Function Pointer:

typedef R (*funcPtr)(P);

And also in case you didn't know, you can in fact define functions with parenthesis around the name, it is valid syntax.

Okay, so what we have here is really simple to use:

void PrintInt(int num)
{
	std::cout << num << endl;
}
typdef StaticDelegate<void,int> SDelegate;

IDelegate<void,int> delegate = SDelegate(&PrintInt);

I do encourage you to use typedefs to make your life easier, this becomes more obvious why you might want to do that when you're working with Class Member Function Pointers. In an event system, to be as flexible as possible, I do suggest trying to stick with one type of delegate.

Okay, the above shows you what you need to do to get a Static Function working with an IDelegate, now we need to get something to encapsulate a Member Function Pointer. For lack of a better name, I'll be using Type Delegate, as you do have to specify a class type that the member function belongs to.

// This class is for attaching a Member Function Pointer to an IDelegate
// R is the return type
// T is the Class of this Member Function Pointer
// P is the parameter type
template <typename R,typename T,typename P>
class TypeDelegate
	: public IDelegate<R,P>
{
	typedef R (T::*funcPtr)(P);// this is a function pointer definition
protected:
	funcPtr _funcPtr;
	T& _objPtr;

public:
	TypeDelegate(T& objPtr, funcPtr functionPointer)
		: _objPtr( objPtr )
		, _funcPtr( functionPointer )
	{
	}

	R Invoke(P param)
	{
		return (_objPtr.*_funcPtr)(param);
	}
};

Okay, so to create a TypeDelegate you need to pass in an object of the class you specified, and a function pointer to the class. So you can try this:

class Object1
{
public:
	typedef TypeDelegate<void,PhysicsObject,Object_Event::ENUM> TDelegate;

	Object1(){}

	void CatchEvent(Object_Event::ENUM eventID)
	{
		//...
	}
};
//  Somewhere in your code...
Object1 obj1 = Object1();

IDelegate delegate = TDelegate(&obj1,&Object1::CatchEvent);

//or inside of obj1:
delegate = TDelegate(*this,&Object1::CatchEvent);

Again, please feel free to use typedefs as it'll leave you with less code to type. I also suggest using this one system for all your delegate needs, no matter if you need parameters or not. I think this system is beautiful as it is because it's so simple, flexible, and it's not hard to tell what it does and didn't require a mess to get working like I thought might have to happen. If you want to use this type of delegate with no params, you can define an empty class like this:

// use as a Parameter type instead of void inside of IDelegate, as you'll get a compiler error
class Void
{
public:
	Void(){}
};

Yes, as I indicated in the comment, if you try to use "void" as the parameter type for the IDelegate sub classes, you will get a compiler error. So, as an alternative, use "Void", and pass in Void() as the args for the delegate; or, just use a "void*" and set it to NULL I guess (or a Void* or whatever you want); just make it obvious to functions using the delegate know that it's a void type.

So, as I suggested for keeping the delegate system simple, when you want to use multiple parameters, just use a struct with them defined. One reason I prefer this: if you didn't use a single struct, you'd have to expect every function variable to be properly named to know what it does. In this case, if you're using a struct, the variables names are what you define them to be, so this will help for documentation purposes when someone wants to find out what it does, they just need to look in one place: the struct definition.

 

[UPDATE:]
Source Files: C++ Delegates.

Comments

There are no comments on this post.

Archive