C++11 has a powerful callback mechanism included in the header functional
It is a strongly typed callback mechanism and as such should know the type of callee object and arguments.
Many a times, we come across situation where we need to invoke a callback(like event handlers) without exactly knowing the type of callee.
We could impose some restrictions on the type of callee, to implement a particular interface.
This is a traditional approach of solving this problem, by using dynamic polymorphism.
Dynamic polymorphism is not recommended for low latency applications, as it involves vtable lookup, every time a function is invoked.
We can create a Functor class which can achieve the same results as dynamic polymorphism, but with the same performance as std::function.
Following is an implemenation of such a functor class.
Functor using variadic templates
Our functor should have a pointer to a callee object and member function object.
It then will have function call operator overloaded to take any number of arguments and just pass it on to the member method.
We will have templates for both the objects.
A simple implementation is shown below:
template<typename Client, typename Memfunc>
class Functor
{
private:
Client* m_client;
Memfunc m_func;
public:
Functor(Client* client, Memfunc memfunc):m_client(client), m_func(memfunc)
{
}
template<typename ...T>
void operator()(T... args)
{
(m_client->*m_func)(args...);
}
};
Usage of this functor is shown below:
class Receiver
{
public:
void singleArg(int i)
{
std::cout<<"Single arg: "<<i<<"\n";
}
void doubleArg(int i, double j)
{
std::cout<<"double arg: "<<i<<":"<<j<<"\n";
}
};
Receiver receiver;
Functor<Receiver, decltype(&Receiver::singleArg)> f1(&receiver, &Receiver::singleArg);
f1(10);
Functor<Receiver, decltype(&Receiver::doubleArg)> f2(&receiver, &Receiver::doubleArg);
f2(10, 3.14);
This works well for invoking member methods with any arguments.
What if we want to save an instance of Functor and invoke it later?
The problem arise as the Functor type is dependent on Client and Memfunc.
Anywhere we need to use Functor, we have to mention Client and Memfunc.
To get rid of this dependency, we can have a base class which could abstract out both the types.
class FunctorBase
{
public:
void *_client;
typedef void ( FunctorBase::*memfunc ) ();
char _ppMemfunc[sizeof ( memfunc )];
FunctorBase(void *cl, void *f):_client(cl)
{
memcpy ( _ppMemfunc, f, sizeof ( memfunc ) );
}
template<typename T>
virtual void operator()(T... args)=0;
};
Alas!! We are getting compilation error, gcc complaining "templates may not be 'virtual'"
We can forward the base class call to derived class by means of static method.
Since static methods are not really associated with object, we can use it for forwarding the call.
Here is the complete implementation
//Encapsulates the callee and member method
//Member method would be copied to a byte array, whcih will be used later to invoke the same.
class CallbackData
{
public:
typedef void ( CallbackData::*memfunc ) ();
CallbackData ( const void* pClient_, const void* ppMemfunc_ ) : _pClient ( pClient_ )
{
memcpy ( _ppMemfunc,ppMemfunc_,sizeof ( memfunc ) );
}
const void* pClient() const
{
return _pClient;
}
const void* ppMemfunc() const
{
return _ppMemfunc;
}
protected:
char _ppMemfunc[sizeof ( memfunc )];
const void* _pClient;
};
//Base class of functor, which just invokes the saved member method.
template<typename ReturnType, typename... Param>
class FunctorBase
{
public:
CallbackData data;
typedef ReturnType (*Fptr)(CallbackData, Param...);
Fptr func;
FunctorBase(const void* client, const void* memfunc, Fptr f):data(client, memfunc), func(f)
{
}
template<typename... T>
ReturnType operator()(T... args)
{
return (func)(data, args...);
}
};
template<typename Client, typename Memfunc, typename ReturnType, typename... Param>
class Functor : public FunctorBase<ReturnType, Param...>
{
public:
//Hooks a public static method to the base class, so that there is no dependency.
//From the static method, callee and member method is determined and method is invoked.
Functor(Client* client, Memfunc* memfunc):FunctorBase<ReturnType, Param...>(client, memfunc, hook)
{
}
template<typename... T>
static ReturnType hook(CallbackData data, T... args)
{
Client* pClient = ( Client* ) data.pClient();
Memfunc& pmemfunc = ( * ( Memfunc* ) ( data.ppMemfunc() ) );
return ( pClient->*pmemfunc ) (args...);
}
};
Now the base class only requires type of the parameters, not the client object or member method.
We can use the functor in the following way:
typedef void (Receiver::*SFP)(int);
SFP temp = &Receiver::singleArg;
FunctorBase *f = new Functor<Receiver, SFP, int>
(&receiver, &temp);
(*f)(10);
The flexibility of the above code is that FunctorBase<type> is not depended on the actual functor implementation.
We can assign any functor objects at the runtime to this base class and get it executed.
So the class which needs to invoke a callback, can have FunctorBase<type>* and can invoke any callback without knowing the type of exact callee object.
Currently we only have support for member methods.
To add support for static methods or non-member methods, we just need to provide a template specialization of our functor.
template<typename Memfunc, typename ReturnType, typename... Param>
class Functor<void*, Memfunc, ReturnType, Param...> : public FunctorBase<ReturnType, Param...>
{
public:
Functor(Memfunc* memfunc):FunctorBase<ReturnType, Param...>(NULL, memfunc, hook)
{
}
template<typename... T>
static ReturnType hook(CallbackData data, T... args)
{
Memfunc& pmemfunc = ( * ( Memfunc* ) ( data.ppMemfunc() ) );
return ( *pmemfunc ) (args...);
}
};
The performance of this functor implementation is almost same as std::function, as there is nothing but just a function call when the callback is invoked.