Yesterday, I was trying to program a basic renderer where the renderer controlled when data was loaded into a shader without the renderable object knowing anything about the shader being used. Being a stubborn person (and not running on enough sleep), I spent several hours trying to have function pointers sent to the renderer, saved, then run at the appropriate time. It wasn't until later that I realized what I was trying to build was a message system. It got me wondering, though, is it possible to save function pointers with arguments directly to be run at a later time in c++.
My original idea looked something like this:
// set up libraries and variables
Renderer renderer();
renderable obj();
mat4 viewMatrix();
// renderer returns and object id
int objID = renderer.loadObj(obj)
int main()
{
//do stuff
while(running)
{
//do stuff
renderer.pushInstruction(//some instruction);
renderer.render();
}
}
// functionPtr.h
#include <functional>
class storableFunction
{
public:
virtual ~storableFunction = 0;
virtual void call() = 0;
};
template<class type>
class functionPtr : public storableFunction
{
std::function<type> func;
public:
functionPtr(std::function<type> func)
: func(func) {}
void call() { func(); }
};
//renderer.h
struct modelObj
{
// model data and attached shader obj
std::queue<storableFunction> instruction;
}
class renderer
{
std::map<int, modelObj> models;
public:
// renderer functions
void pushInputDataInstruction(int id, //function, arg1, arg2);
// this was overloaded because I did not know what type the second argument would be
// pushInputDataInstruction implementation in .cpp
{
models[id].instruction.push(functionPtr(std::bind(//method with args)))
}
void render();
};
//implantation in .cpp
{
for(// all models)
//bind all data
applyInstructions(id);
// this would call all the instructrions using functionptr.call() in the queue and clear the queue
draw();
// unbind all data
}
I realize that boost probably supports some kind of similar functionality, but I wanted to avoid using boost.
Is something like this possible, what would the general design look like, and what would it even be used for seeing as a message bus is a much more proven design pattern for something like this?
Is it possible to save function pointers with arguments directly to be run at a later time in c++.
Yes, it is.
First of all, if you are in already C++11 or latter you do not need boost to deal with your current problem.
The simplest and intuitive approach is to, make all your functions as a lambda function (i.e, return lambdas) and store to your
std::queue<storableFunction> instruction;
You will find a detailed explanation about lambdas here: What is a lambda expression in C++11?
Providing storableFunction
idea is good as you can tell the function pointer type explicitly for each member functions which you are storing to the modelObj
.
However, in case of thinking of storing to some STL containers, you need to use std::function
, with some type erasure overhead, which can deal with the different lambda functions, capable of capturing variables in scope).
Here is an example code with std::vector
#include <iostream>
#include <vector>
#include <functional>
int main()
{
int arg1 = 4;
std::string arg2 = "String";
std::vector<std::function<void()>> vecFunPtr
{
[](int arg1 = 1){ std::cout << arg1 << std::endl; },
[](float arg1 = 2.0f){ std::cout << arg1 << std::endl; },
[](double arg1 = 3.0){ std::cout << arg1 << std::endl; },
[&arg1, &arg2](){ std::cout << arg1 << " " << arg2 << std::endl; }
};
for(const auto& funs: vecFunPtr) funs(); // call the stored lambdas
return 0;
}
Output:
1
2
3
4 String
In your case, Renderer
can be written as follows. One thing to note that, you need to make a bit workaround for passing different arguments to the member functions(or lambda captures for sure.)
Side-note: Here you will find some tips to avoid the performance issues due to std::function
, which might be helpful.
class Renderer
{
typedef std::queue<std::function<void()>> modelObj; // you might need modelObj for only this class
typedef std::function<void()> fFunPtr; // typedef for void(*)() using std::function
std::map<int, modelObj> models;
public:
// renderer functions can be written like returning a lambda
fFunPtr rFun1(int arg1) { return [](int arg1 = 1){ std::cout << arg1 << std::endl; }; }
fFunPtr rFun2(double arg1) { return [](float arg1 = 2.0f){ std::cout << arg1 << std::endl; }; }
// function to store them for latter use
void pushInputDataInstruction(const int id, const fFunPtr& funPtr)
{
models[id].push(funPtr);
}
};
int main()
{
Renderer objRender;
//do stuff
while(/*condition*/)
{
//do stuff
objRender.pushInstruction(/* id number, function pointer*/)
renderer.render();
}
return 0;
}