Jump to content
  • Advertisement
Sign in to follow this  
gretty

Fun Challenge: Implement Polymorphic Callback

This topic is 905 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, I am implementing Component Based Architecture in my application and have recently run into a problem. The problem could be easily solved by using std::function although as a fun project I would like to try and tackle this problem without using std::function (even if that means ending up implementing my own version of std::function).

_

Problem:

I'm at the stage where I need to allow a Component to specify a callback that will be executed upon an event.

typedef void(Component::*EventCallback) ();
typedef std::pair<Component*, EventCallback> EventDelegate;

_

The problem with the above type definition is that all components inherit from Component but will never be a direct Component. So the following line of code is not valid:

MoveComponent* mc = new MoveComponent(); // inherits from Component
EventDelegate ed(mc , &MoveComponent::moveToXY); // Not valid because it expects Component* not MoveComponent*, and same for EventCallback.

_

Any ideas on techniques and approaches that could achieve this?  Some approaches I am thinking of... any obvious/other solutions I have missed?

typedef void* EventCallback; // lazy solution 

// or

// Psuedo code (I've considered using templates here but its doesn't seem to be a solution)
class EventDelegate
{
private:
	// DataType = Pointer to component type (eg MoveComponent) 
	// Func		= Pointer to component function 
	// Context	= Pointer to component

public: 
	void runCallback()
	{
		(Context->*(void(DataType::*Func)) ();
	}
};

std::unordered_map<EVENT, EventDelegate> evtRegistry;

_

Current (simplified) implementation:

typedef void(Component::*EventCallback) ();
typedef std::pair<Component*, EventCallback> EventDelegate;

class Component {
    // ...
};

class MoveComponent : public Component {
public:
    MoveComponent() {

        EventDelegate ed(this, &MoveComponent::moveToXY);
        ComponentManager::registerEvent(ed);
    }

    void moveToXY() { }
};

class ComponentManager {
public:

    static void registerEvent(EventDelegate ed) {
        evtRegistry.push_back(ed);
    }

    static void runEvent(EventDelegate ed) {
        for (int i=0; i<evtRegistry.size(); i++) {
            Component* context = evtRegistry.at(i).first;
            EventCallback ec = evtRegistry.at(i).second;
            context->*ec();
        }
    }

private:
    static std::vector <EventDelegate> evtRegistry; // vector for a simple example. Really a std::unordered_multimap<EVENT, EventDelegate>
};
Edited by gretty

Share this post


Link to post
Share on other sites
Advertisement
class IDelegate {
public:
    virtual void invoke() = 0;
};

template<class T>
class Delegate : public IDelegate {
    T* instance;
    void (T::*method)();

public:
    Delegate(T* inst, void (T::*met)()) : instance(inst), method(met) {
    }

    void invoke() {
        (instance->*method)();
    }
};

// ---------------------

class Callable {
public:
    void foo() {
        std::cout << "foo called\n";
    }
};

Callable instance = new Callable();
IDelegate del = new Delegate<Callable>(instance, &Callable::foo);
del->invoke();

 
 
This uses a virtual call but is type safe.

EDIT: There is also the FastDelegate library by Don Clugston which uses some (apparently widely supported) hacks to achieve the same without virtual function calls. I don't know if the library is still recommended as it was implemented 10 years ago... I only know that the Spotify client on windows has it listed on its credits screen. smile.png Edited by Madhed

Share this post


Link to post
Share on other sites

I made this when I realized C++ is lacking in that department

//Supports nonmember and member functions, lambdas (captured parameters are not stored!)
		template<typename ReturnT, typename... ParamT>
		class CallbackBase
		{
			//Member func as free function
			using MFuncT = ReturnT(*)(void*, ParamT...);
			//free function
			using FuncT = ReturnT(*)(ParamT...);
		public:
			CallbackBase()
			{ 
				reset();
			}
			template<typename ClassT, ReturnT(ClassT::*memberFunc)(ParamT...)>
			void connectMemberFunction(ClassT* object)
			{
				mObject = object;
				mMemberFunction = &memberFunctionPtrAsFreeFunction < ClassT, decltype(memberFunc), memberFunc, ParamT... > ;
			}
			template<typename LambdaT>
			//NOTE: Callback lifetime must not exceed that of lambda if variables are captured! Use connectFunction if lambda is noncapturing
			void connectLambda(LambdaT& lambda)
			{
				mObject = &lambda;
				mMemberFunction = &memberFunctionPtrAsFreeFunction < LambdaT, decltype(&LambdaT::operator()), &LambdaT::operator(), ParamT... > ;
			}
			void connectFunction(ReturnT(*func)(ParamT...))
			{
				mObject = nullptr;
				mFunction = func;
			}
			void reset()
			{
				mObject = nullptr;
				mFunction = nullptr;
			}
			//false for normal function, true for member function / lambda
			bool hasContext() const
			{
				return mObject != nullptr;
			}
			bool connected() const
			{
				return mFunction != nullptr;
			}
		protected:
			template<typename ClassT, typename MemberFuncT, MemberFuncT memberFunc, typename... SuppliedParamT>
			static ReturnT memberFunctionPtrAsFreeFunction(void* object, SuppliedParamT... params)
			{
				return (static_cast<ClassT*>(object)->*memberFunc)(params...);
			}
			void* mObject = nullptr;
			union
			{
				MFuncT mMemberFunction;
				FuncT mFunction;
			};
		};








		template<typename>
		class Callback;
		template<typename... ParamT>
		//Supports nonmember and member functions, lambdas (captured parameters are not stored!) [void return type]
		class Callback<void(ParamT...)> : public CallbackBase < void, ParamT... >
		{
			using CallbackT = Callback<void(ParamT...)>;
		public:
			void connectToStub()
			{
				connectFunction(&stub);
			}
			template<typename... SuppliedParamT>
			void operator()(SuppliedParamT&&... params)
			{
				assert(mFunction != nullptr);
				//Member function or lambda
				if (mObject != nullptr)
				{
					(*mMemberFunction)(mObject, std::forward<SuppliedParamT>(params)...);
				}
				//Nonmember function
				else
				{
					(*mFunction)(std::forward<SuppliedParamT>(params)...);
				}
			}
			explicit operator bool() const { return connected(); }
			bool operator==(const CallbackT& other) const { return mFunction == other.mFunction && mObject == other.mObject; }
			bool operator!=(const CallbackT& other) const { return !((*this) != other); }
		private:
			static void stub(ParamT...)
			{
			}
		};








		template<typename ReturnT, typename... ParamT>
		//Supports nonmember and member functions, lambdas (captured parameters are not stored!) [nonvoid return type]
		class Callback<ReturnT(ParamT...)> : public CallbackBase < ReturnT, ParamT... >
		{
			using CallbackT = Callback<ReturnT(ParamT...)>;
		public:
			template<ReturnT defaultValue = ReturnT()>
			void connectToStub()
			{
				connectFunction(&stub<defaultValue>);
			}
			template<typename... SuppliedParamT>
			ReturnT operator()(SuppliedParamT&&... params)
			{
				assert(mFunction != nullptr);
				//Member function or lambda
				if (mObject != nullptr)
				{
					return std::move((*mMemberFunction)(mObject, std::forward<SuppliedParamT>(params)...));
				}
				//Nonmember function
				else
				{
					return std::move((*mFunction)(std::forward<SuppliedParamT>(params)...));
				}
			}
			explicit operator bool() const { return connected(); }
			bool operator==(const CallbackT& other) const { return mFunction == other.mFunction && mObject == other.mObject; }
			bool operator!=(const CallbackT& other) const { return !((*this) == other); }
		private:
			template<ReturnT defaultValue>
			static ReturnT stub(ParamT...)
			{
				return defaultValue;
			}
		};

Its just a pointer to a function and a pointer to an object (which is null if its free function). Ive only done primitive testing on it so it might be breaking some rule somewhere, but maybe you get ideas.

Share this post


Link to post
Share on other sites

I made this when I realized C++ is lacking in that department

 

[...]

 

Its just a pointer to a function and a pointer to an object (which is null if its free function). Ive only done primitive testing on it so it might be breaking some rule somewhere, but maybe you get ideas.

 

You are relying on vendor specific behaviour. :)

Calling conventions are up to the compiler and platform. So calling the member function as a free function with an implicit this argument might fail.

 

But as long as it works...

It's the same trick that the FastDelegate library uses. Only it has compile switches for a dozen compilers or so. :D

Share this post


Link to post
Share on other sites


You are relying on vendor specific behaviour.
Calling conventions are up to the compiler and platform. So calling the member function as a free function with an implicit this argument might fail.
 
But as long as it works...
It's the same trick that the FastDelegate library uses. Only it has compile switches for a dozen compilers or so.

 

I dont specify the calling convention (I never convert the member function pointer to something else and only call it knowing both the class type and member func ptr type)

 

AFAIK the only requirement is that the member function pointer is compile time constant (as its used as template argument to 'wrap' it into normal nonmember function). Shouldnt compile otherwise...

 

Im not sure if the above assumption is true for something like virtual methods (it COULD be?). But yeah I dont feel confident using it for anything else than regular member functions which I can assume are known at compile time (better be, for my performances sake)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!