在一个函数容器中存储和调用不同参数的函数

Storing and calling functions of different arguments in one function container

本文关键字:函数 存储 调用 参数 一个      更新时间:2023-10-16

下面是我想要实现的一个示例设计代码。基本上,我想为不同的handlerNames存储处理程序函数,这些处理程序函数可以是可变参数。

应在事件上调用处理程序函数,所需参数通过Script::Handle(…)传递

我怎样才能做到这一点?也许使用变分模板是可能的?

class Script
{
public:
    Script() { /* ... */ }
    template<typename TFunction>
    void AddHandler(const char *handlerName, TFunction &&function)
    {
        _handlerMap[handlerName] = std::move(function);
    }
    void Handle(const char *handlerName, ...)
    {
        _handlerMap[handlerName](...);
    }
private:
    typedef std::map<std::string, std::function<void()>> HandlerMapType;
    HandlerMapType _handlerMap;
};
//Handler functions
handlerOne() { std::cerr << "One"; }
handlerTwo(std::string a1, int a2) { std::cerr << "Two"; }
handlerThree(bool a1) { std::cerr << "Three"; }
int main(int argc, char **argv)
{
    Script script;
    script.AddHandler("One", std::bind(&handlerOne));
    script.AddHandler("Two", std::bind(&handlerTwo));
    script.AddHandler("Three", std::bind(&handlerThree));
    script.Handle("One");
    script.Handle("Two, "string", 96);
    script.Handle("Three", true);
    script.Handle("Three", "what should happen here?"); //String passed instead of bool
}

让我在前缀中说,这在C++中不是一件小事。我要说的是,你应该考虑这是否真的是你在用例中需要的东西。在您的例子中,您要求的是泛型,但您不能真正使用它。在任何情况下,您都需要知道正在调用的函数的签名才能正确调用它;在这种情况下,把它们放在容器里有什么作用?

一般来说,如果您正在编写中间层的代码,您会执行类似的操作。在您的示例中,这相当于编写允许另一个用户调用Handle的代码。一个常见的具体例子是编写一个工厂,其中可以使用不同的参数实例化工厂中的对象。然而,这不可能真的是"不同"的论点,至少没有一些疯狂的选角。解决方案是使所有函数采用相同的参数,但使参数成为一种动态类型,可以存储您想要的任何参数:

using argument_type = std::unordered_map<std::string, boost::any>;
void print(const argument_type & arg) {
  auto to_print = boost::any_cast<std::string>(arg["to_print"]);
  std::cerr << to_print << std::endl;
}
void print_none(const argument_type & arg) {
      std::cerr << "none" << std::endl;
}
using my_func_t = std::function<void(const argument_type &)>;
std::vector<my_func_t> v;
v.emplace_back(print);
v.emplace_back(print_none);
// create some argument_types, feed them to f.

上面的代码没有经过测试,也没有工作的main,但我认为这应该让你知道如何实现你想要的。

编辑:我想了更多,我决定详细阐述一下"疯狂选角"的方式。我想这并不是真的更疯狂,但我更喜欢我上面展示的。另一种选择是完全键入擦除函数本身,并使用可变模板传递参数。

void print(std::string to_print) {
  std::cerr << to_print << std::endl;
}
void print_none() {
      std::cerr << "none" << std::endl;
}

std::vector<boost::any> v;
v.emplace_back(std::function<void(std::string)>(print));
v.emplace_back(std::function<void(void)>(print_none));
template <typename ... Args>
void call(const std::vector & funcs, int index, Args... args) {
  auto f = boost::any_cast<std::function<void(Args...)>>(funcs[index]);
  f(std::forward<Args>(args)...);
}
// unsure if this will actually work
call(f, 0, std::string("hello"));

不过,上面的代码非常脆弱,因为传递给调用的类型将被推导出来,然后强制转换将尝试强制转换为与该签名匹配的std::函数。那个确切的签名。我不太相信这会成功;如果它是引用、vs值、vs右值等。返回到与您所放入的不同的std::函数是未定义的行为。

总之,我要么尽量避免完全这样做,要么选择第一个解决方案。它的脆弱性要低得多,而且最好坦率地说你正在擦除这些函数的签名。