【问题标题】:Is it possible to save a function pointer with arguments for later use?是否可以保存带有参数的函数指针以供以后使用?
【发布时间】:2018-08-28 16:59:38
【问题描述】:

昨天,我尝试编写一个基本渲染器,其中渲染器控制数据何时加载到着色器中,而可渲染对象不知道有关正在使用的着色器的任何信息。作为一个固执的人(并且没有足够的睡眠),我花了几个小时试图将函数指针发送到渲染器,保存,然后在适当的时间运行。直到后来我才意识到我正在尝试构建的是一个消息系统。这让我想知道,是否可以直接保存带有参数的 函数指针,以便以后在 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

这样的事情是否可能,一般设计会是什么样子,甚至可以将其用于将什么视为消息总线对于这样的事情是一种更加成熟的设计模式?

【问题讨论】:

标签: c++ function class function-pointers


【解决方案1】:

std::bind 是一种方法,但如果您可以访问 C++ 11 及更高版本,则可能需要考虑使用 lambdas。 Scott Meyer 建议在 Effective Modern C++ 中使用它们而不是 std::bind(在大多数情况下)。

一个 lambda 包含三个部分:

  • [] 部分,用于标识要捕获的值或引用,
  • () 部分,用于标识稍后将在调用 lambda 时提供的参数。
  • {} 部分,用于标识如何处理捕获的值和参数

简单示例:

#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 的引用,请使用[&amp;x]。在上面的示例中,结果将是 17(即 15 + 2)。如果确实使用了引用,请注意不要让 xstoredFunction 之前超出范围,因为它会成为对垃圾数据的悬空引用。

现在大多数编译器都支持 C++ 11,但您可能需要在项目设置中明确添加支持:

  • Visual Studio:项目属性/C++/语言/C++ 语言标准
  • gcc:--std=c++11(或 14,或 17...)
  • CMake 还允许您设置标准:set (CMAKE_CXX_STANDARD 11)

【讨论】:

  • 如果我理解正确,您可以创建一个调用您想要的函数的 lambda,然后保存 lambda。对吗?
  • 澄清一下:在 lambda 的创建过程中,不会立即调用该函数。创建 lambda 将进行调用所需的信息(参数 + 函数调用)放在一起。然后使用 () “调用”/调用 lambda。
  • 如果我没记错的话(在这里挥舞着大手),在幕后,创建一个 lambda 会创建一个类,该类存储传递给 [] 的所有值和引用,并创建一个 operator() 接受lambda 开头的 () 中描述的参数。 lambda 的 {} 主体成为 operator() 的逻辑。基本上。
  • 澄清一下:std::bind 也需要 C++11。
【解决方案2】:

是否可以将带有参数的函数指针直接保存为 稍后在 c++ 中运行。

是的,是的。

首先,如果您已经使用 C++11 或更高版本,则不需要 boost 来处理当前的问题。

最简单直观的方法是将所有函数作为 lambda 函数(即返回 lambda)并存储到您的

std::queue<storableFunction> instruction;

你可以在这里找到关于 lambdas 的详细解释:What is a lambda expression in C++11?


提供storableFunction 的想法很好,因为您可以为每个要存储到modelObj 的成员函数明确地告知函数指针类型。

但是,如果考虑存储到某些 STL 容器中,则需要使用std::function,以及一些type erasure overhead,可以处理the different lambda functions,能够捕获范围内的变量)。

这里是 an example codestd::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 可以写成如下。需要注意的一点是,您需要采取一些解决方法来将不同的参数传递给成员函数(或者肯定是 lambda 捕获。)

旁注Here 你会发现一些技巧来避免由于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;
}

【讨论】:

  • 我觉得这很有意义!我仍然想知道您是否有任何理由在更传统的消息系统上使用这种设计模式?
  • @JustinW 原因很简单:Lambda 函数的灵活性,它的定义可用,我们需要它们。另一方面,您也可以寻找信号槽机制,例如vdk-signalboost-signals2,它实际上可以按您的预期工作。 (如果你不想使用 lambdas)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-04
  • 2016-07-15
  • 2010-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多