Можно ли сохранить указатель на функцию с аргументами для последующего использования? - PullRequest
0 голосов
/ 28 августа 2018

Вчера я пытался запрограммировать базовый рендерер, который бы управлял рендерингом, когда данные загружались в шейдер, а объект рендеринга ничего не знал об используемом шейдере. Будучи упрямым человеком (и не бегающим в достаточном количестве сна), я потратил несколько часов, пытаясь получить указатели функций, отосланные рендереру, сохранить, а затем запустить в подходящее время. Только позже я понял, что я пытался создать систему сообщений. Мне стало интересно, возможно ли сохранить указатели на функции с аргументами, которые будут позже запущены в c ++.

Моя оригинальная идея выглядела примерно так:

//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
}

Я понимаю, что boost, вероятно, поддерживает какую-то похожую функциональность, но я хотел бы избежать использования boost .

Возможно ли что-то подобное, как будет выглядеть общий дизайн, и что бы он даже использовал, чтобы видеть, как шина сообщений является гораздо более проверенным шаблоном проектирования для чего-то подобного?

Ответы [ 2 ]

0 голосов
/ 28 августа 2018

Можно ли сохранять указатели функций с аргументами, чтобы запустить позже в C ++.

Да , это так.

Прежде всего, если вы уже используете C ++ 11 или более позднюю версию, вам не нужно повышать уровень, чтобы справиться с текущей проблемой.

Самый простой и интуитивно понятный подход - сделать все ваши функции лямбда-функцией (т.е. вернуть лямбда-выражения) и сохранить их в

std::queue<storableFunction> instruction;

Подробное описание лямбды вы найдете здесь: Что такое лямбда-выражение в C ++ 11?


Предоставление идеи storableFunction хорошо, поскольку вы можете явно указывать тип указателя функции для каждой функции-члена, которую вы храните в modelObj.

Однако, если вы хотите сохранить некоторые контейнеры STL, вам нужно использовать std::function с некоторыми издержками на стирание типа , которые могут иметь дело с различные лямбда-функции , способные захватывать переменные в области видимости).

Вот пример кода с 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;
}

выход

1
2
3
4 String

В вашем случае, Renderer можно записать следующим образом. Стоит отметить, что вам нужно сделать обходной путь для передачи различных аргументов функциям-членам (или, конечно, лямбда-захватам).

Примечание : Здесь вы найдете несколько советов, чтобы избежать проблем с производительностью из-за std::function, которые могут быть полезны.

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;
}
0 голосов
/ 28 августа 2018

std::bind является одним из подходов, но если у вас есть доступ к C ++ 11 и более поздним версиям, вы можете вместо этого рассмотреть возможность использования лямбда-выражений. Скотт Мейер рекомендует использовать их вместо std :: bind (в большинстве случаев) в Effective Modern C ++ .

Лямбда состоит из трех частей:

  • часть [], которая определяет значения или ссылки для захвата,
  • часть (), которая определяет аргументы, которые будут предоставлены позже, когда будет вызываться лямбда.
  • часть {}, которая определяет, что делать с захваченными значениями и параметрами

Простой пример:

#include <iostream>

void printValue(int x) {
    std::cout << x << std::endl;
}

int main(int argc, char * argv[]) {
    int x = 23;

    // [x] means 'capture x's value, keep it for later'
    // (int y) means 'I'll provide y when I invoke the lambda'
    auto storedFunction = [x](int y){return printValue(x + y);};

    x = 15;

    // Now we invoke the lamda, with y = 2
    // Result: 25 (23 + 2), even if x was changed after the lambda was created
    storedFunction(2); 
    return 0;
}

Если вы хотите захватить ссылку на x, используйте [&x]. В приведенном выше примере результат будет равен 17 (то есть 15 + 2). Если вы используете ссылку, будьте осторожны, чтобы не допустить, чтобы x выпал из области видимости до storedFunction, так как это могло бы стать висячей ссылкой на мусорные данные.

Большинство компиляторов теперь поддерживают C ++ 11, но вам может понадобиться добавить поддержку явно в настройках проекта:

  • Visual Studio: Свойства проекта / C ++ / Язык / Стандарт языка C ++
  • gcc: --std=c++11 (или 14, или 17 ...)
  • CMake также позволяет установить стандарт: set (CMAKE_CXX_STANDARD 11)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...