Search code examples
c++functionclassc++11function-pointers

Is it possible to save a function pointer with arguments for later use?


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?


Solution

  • 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;
    }