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

时间:2015-06-25 00:49:15

标签: c++

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

应该在事件上调用处理函数,并使用Script :: Handle(...)

传递所需的参数

我怎样才能做到这一点?也许它可能与Variadic模板?

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
}

1 个答案:

答案 0 :(得分:2)

让我用前缀说这在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.

以上不是经过测试的代码,也不是有效的代码,但我认为这应该让您了解如何实现自己想要的目标。

编辑:我考虑了一下,我决定详细说明“疯狂铸造”的方式。我想它并不是真的更疯狂,但我更喜欢上面展示的内容。另一种方法是完全键入擦除函数本身,并使用可变参数模板传递参数。

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 :: function。那个确切的签名。我不太相信这会成功;如果它是一个引用,vs值,vs rvalue等,则返回到与你输入的不同的std :: function是未定义的行为。

总之,我要么尽量避免完全这样做,要么选择第一个解决方案。它不那么脆弱,而且最好先关注你正在删除这些函数的签名。