【问题标题】:std::function accepting different function signatures in c++std::function 在 C++ 中接受不同的函数签名
【发布时间】:2018-01-01 08:00:22
【问题描述】:

我正在尝试创建一个类似于 c# 系统的 c++ 事件系统。 我需要能够存储任何类型的函数并在适当的时候使用正确的参数调用它们。

我能得到的最接近的是 std::function、bind 和占位符,但这是我的问题。

void Func()
{
    std::cout << "Notified" << std::endl;
}

void FuncWithParam(const std::string& str)
{
    std::cout << str << std::endl;
}

std::function<void()> fn = std::bind(Func); // this works

std::function<void()> fn = std::bind(FuncWithParam, "Hello there"); // this works also

std::function<void()> fn = std::bind(FuncWithParam, _1); // but this doesn't

真的可以在单个 std::function 中存储任何类型的签名吗?还是我必须求助于更复杂的解决方案。

【问题讨论】:

  • 不是任何一种,不。您在模板参数中指定一个签名。可转换性使类型可以协商,但函数参数的数量是固定的。
  • 可以构建这样的东西,但问题仍然是,当您可以将任何可调用对象放入其中时,您如何知道您正确调用了它?也许您正在寻找类似type-safe heterogeneous event system 的东西。
  • 我希望不使用 Boost,但您的回答实际上为我正在尝试做的事情命名。这应该让我继续前进
  • FWIW,Hana 在加入 Boost 之前是独立存在的。完全可以在没有 Boost 的情况下使用它。
  • 哦,这不仅仅是关于 Boost。我希望能够在不使用任何外部依赖项的情况下解决这个问题。但如果需要,我肯定会尝试 Hana 甚至 Boost::Signal。

标签: c++ c++11 events std-function stdbind


【解决方案1】:

这是一个简单的 C++ 广播器:

using token = std::shared_ptr<void>;

template<class...Ts>
struct broadcaster {
  using listen = std::function<void(Ts...)>;
  using sp_listen = std::shared_ptr<listen>;
  using wp_listen = std::weak_ptr<listen>;

  token attach( listen l ) {
    return attach( std::make_shared<listen>(std::move(l)) );
  }
  token attach( sp_listen sp ) {
    listeners.push_back(sp);
    return sp;
  }
  void operator()(Ts...ts)const {
    listeners.erase(
      std::remove_if( begin(listeners), end(listeners),
        [](auto&& wp){return !(bool)wp.lock();}
      ),
      end(listeners)
    );
    auto tmp = listeners;
    for (auto&& l : tmp) {
      if (auto pf = l.lock()) {
        (*pf)(ts...);
      }
    }
  }
private:
  mutable std::vector<wp_listen> listeners;
};

要收听它,你 .attach 并传递一个函数来调用它。 attach 返回 token,只要该令牌(或其副本)继续存在,就会调用该函数。

要调用消息,请在 broadcaster 上调用 ()

下次调用广播器时,会回收死回调的内存;间接拥有的资源清理得更快。

如果你在当前广播的时候注册一个监听器,它不会得到当前的广播。

您可以添加std::mutex 以使其可同时从多个线程中使用,或在外部同步。如果您在内部进行同步,当您在 () 中运行 for(auto&amp;&amp; 循环以避免重入问题时,我不会持有互斥锁。

使用示例:

struct location {
  int x, y;
};
struct button {
  broadcaster< location > mouse_click;
  broadcaster<> mouse_enter;
  broadcaster<> mouse_leave;
};

struct dancer {
  std::vector<token> listen_tokens;
  dancer( button& b ) {
    listen_tokens.push_back( b.mouse_enter.attach([this]{ dance(); } ) );
    listen_tokens.push_back( b.mouse_leave.attach([this]{ end_dance(); } ) );
    listen_tokens.push_back( b.mouse_click.attach(
      [this](location l){
        pose(l.x, l.y);
      }
    ) );
  }
  void dance() const {
    std::cout << "start dancing\n";
  }
  void pose( int x, int y ) const {
    std::cout << "struck a pose at " << x << ", " << y << "\n";
  }
  void end_dance() const {
    std::cout << "end dancing\n";
  }
};

请注意,没有使用 virtual 方法。唯一的多态性是基于std::function 的类型擦除。

监听对象必须跟踪它们要明确监听的生命周期(通过保持 token 活动),如果它们希望能够取消注册到特定广播公司,它们必须自己保持该关联。

如果广播公司先走,没有问题。如果听众离开,只要他们的令牌在他们之前,一切都很好。附加确实会导致动态分配来存储令牌(以及移动到侦听器的副本),但只有一个。

这与您在 C# 中使用的方法不同,既因为它依赖于 RAII,又因为它的核心不是 OO,但仍然是多态的。

【讨论】:

  • 看起来像我要找的东西。我很难让它与可变参数模板一起工作。互斥锁也是一个不错的选择。如果我在实施它时遇到一些困难,我会及时通知你。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-04-02
  • 1970-01-01
  • 2014-11-27
  • 2014-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多