Here is my full implementation of a generic Functor-like class:
#ifndef FUNCTIONHPP
#define FUNCTIONHPP
#include "Types.hpp"
#include <tuple>
#include <utility>
BEGIN_NAMESPACE //This is a define in "Types.hpp"
namespace FN_HELPERS
{
template<int ...>
struct seq
{
};
template<int N,int ...S>
struct gens : gens<N-1,N-1,S...>
{
};
template<int ...S>
struct gens<0, S...>
{
typedef seq<S...> type;
};
//Global Function Pointer Return Type Identification
template <typename R>
struct returnType;
template <typename R, typename ...Args>
struct returnType<R (*)(Args...)>
{
typedef R type;
};
template <typename R,typename ...Args>
struct returnType<R (*)(Args...,...)>
{
typedef R type;
};
};
//A generic functor to pass between threads
class Function
{
public:
Function() {}
virtual ~Function() {}
Function(const Function&) = delete;
void operator=(const Function&) = delete;
virtual void call()=0;
virtual void* returnValue()=0;
};
template <typename F,typename R,typename ...Args>
class GlobalFunction : public Function
{
public:
GlobalFunction(F func,Args&&... args) : m_func(func),
m_args(std::forward<Args>(args)...)
{
}
virtual ~GlobalFunction()
{
}
GlobalFunction(const GlobalFunction&) = delete;
void operator=(const GlobalFunction&) = delete;
virtual void call()
{
callFunction(typename FN_HELPERS::gens<sizeof...(Args)>::type());
}
virtual void* returnValue()
{
return static_cast<void*>(&m_returnValue);
}
template<int ...S>
void callFunction(FN_HELPERS::seq<S...>)
{
m_returnValue = (*m_func)(std::get<S>(m_args) ...);
}
private:
F m_func;
R m_returnValue;
std::tuple<Args...> m_args;
};
template <typename F,typename ...Args>
class GlobalFunction<F,void,Args...> : public Function
{
public:
GlobalFunction(F func,Args&&... args) : m_func(func),
m_args(std::forward<Args>(args)...)
{
}
virtual ~GlobalFunction()
{
}
GlobalFunction(const GlobalFunction&) = delete;
void operator=(const GlobalFunction&) = delete;
virtual void call()
{
callFunction(typename FN_HELPERS::gens<sizeof...(Args)>::type());
}
virtual void* returnValue()
{
return (void*)0x0;
}
private:
F m_func;
std::tuple<Args...> m_args;
template<int ...S>
void callFunction(FN_HELPERS::seq<S...>)
{
(*m_func)(std::get<S>(m_args) ...);
}
};
//Grab return value helper
//T - Return Type, PTR - Function* pointer
template <typename T>
T RET(Function* f)
{
return *(static_cast<T*>(f->returnValue()));
}
template <typename F,typename ...Args>
Function* _wrapGFn(F func,Args&&... args)
{
return new GlobalFunction<decltype(func),typename FN_HELPERS::returnType<F>::type,Args...>(func,std::forward<Args>(args)...);
}
//Create function object
//F - Function Pointer
#define S_FN(F, ... ) \
_wrapGFn(F, ##__VA_ARGS__)
END_NAMESPACE //This is a #define in "Types.h"
#endif
I use this like so:
//This code wants to schedule a task (function) for execution in parallel on another thread.
Function* f = S_FN(&someFunction,someArg);
//Nanomsg send functions
nn_send(sockfd,(void*)f,sizeof(f),0); //Send it
The thread that receives the task, just runs it, and doesn't care about what it's calling, the return type, or the arguments.
//Recv
void* buf;
nn_recv(sockfd,buf,sizeof(Function*),0);
Function* f = (Function*)buf;
f->call();
//Send back "I'm done" message or the like.
This is for a simple thread pool, it works fine, but the efficiency of the code is what is bothering me. I use nanomsg to avoid mutexes and locks. It's like a mini zeroMQ I suppose. I like this approach because it's cheap on the message size (although there are no copies using nanomsg so it's kind of irrelevant, the zero copy code was omitted to keep this huge post shorter), and I do not need any locks or mutexes between threads.
The drawback is, these functions are called millions of times a second. The scheduler can handle the routing just fine, but at this rate, I'm worried that the construction cost of the function objects and the virtual call are adding too much overhead. (Most of the functions are relatively simple and are completed quickly)
I've looked into some methods (CRTP, static polymorphism, etc...) to try to eliminate the virtual call, but I do not see a way around this. The only reason I ask is because the code gets called so often and I'm wondering if there is a simpler way to do this that would avoid the overhead.
Is there a better way to do this? Is this design flawed in a way I haven't realized?