【问题标题】:Storing and calling functions of different arguments in one function container在一个函数容器中存储和调用不同参数的函数
【发布时间】:2015-06-25 10:17:27
【问题描述】:

这是我想要实现的示例设计代码。基本上我想为不同的 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++


【解决方案1】:

让我先说在 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.

以上代码不是经过测试的代码,也不是可以正常工作的代码,但我认为这应该让您了解如何完成您想要的。

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

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"));

不过,上面的代码非常脆弱,因为您传递给 call 的类型将被推断出来,然后强制转换将尝试强制转换为与该签名匹配的 std::function。那个确切的签名。我不太相信这会奏效。如果它是引用、vs 值、vs rvalue 等。转换回与您输入的内容不同的 std::function 是未定义的行为。

总之,我要么尽量避免需要完全这样做,要么采用第一个解决方案。它的脆弱性要小得多,最好提前说明您正在擦除这些函数的签名。

【讨论】:

  • 投票。我以前遇到过这个问题,我使用可变参数模板和 boost:: 任何喜欢你的解决方案。
  • 好吧,我的第一个解决方案根本不使用可变参数。那你有没有使用第二种解决方案?就我个人而言,我可能不想使用这样的代码,感觉很脆弱,但如果你有的话,我很想听听你的经历。
  • 是的,我使用第二种解决方案。我个人可能也不想使用这样的代码,我非常同意你的看法。我使用抽象工厂模式的第二种解决方案,它可以注册不同的签名函数来创建产品。
  • @NirFriedman 我看到了一种创建延迟调用函数的方法,它在 std::function 中存储了一个任意参数函数。 std::forward/std::bind 组合能否产生更好的代码?这是我看到的stackoverflow.com/questions/14833129/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-01
  • 2021-12-18
  • 1970-01-01
  • 2014-11-19
  • 2019-10-31
  • 1970-01-01
相关资源
最近更新 更多