【问题标题】:Find and delete a std::function object from a map of vectors从向量映射中查找并删除 std::function 对象
【发布时间】:2017-11-12 13:38:51
【问题描述】:

我目前正在尝试为我的游戏引擎实现一个信使系统。它使用以下形式的函数回调:

typedef std::function<void(const Message &)> Callback;

它维护一个消息列表:

mutable std::vector<std::unique_ptr<Message>> messageList;

还有一个签名的回调字典:

mutable std::map<std::string, std::vector<Callback>> callbackDictionary;

用于将所有回调“绑定”到特定的消息类型。调用回调函数时,会传递相应的消息。到目前为止一切顺利。

为了更好地理解,这里是 subscribe 方法,它允许用户添加一个函数方法,该方法为订阅类型的每条消息调用。

void Messenger::Subscribe(Message::Type type, Callback callbackFunction) 
const
{
    callbackDictionary[type].push_back(callbackFunction);
}

这里有一个例子说明它是如何使用的(通过可点击组件类)

messageBus.Subscribe("Message/Click", [this](const Message & message) {this->OnClick(static_cast<const ClickMessage &>(message)); });

现在我的问题:

我想实现一个取消订阅方法,该方法在字典的向量中找到一个函数/方法并将其删除。请注意,该函数可以订阅一种以上的消息类型。我在想这样的事情:

void Messenger::UnSubscribe(Callback callbackFunction)
{
    for (auto & pair : callbackDictionary)
    {
        auto & functionList = pair.second;
        for (auto function : functionList)
        {
            if (function == callbackFunction)
                functionList.erase(std::remove(functionList.begin(), functionList.end(), function), functionList.end());
            // I am aware of issues that may appear from looping deleting while looping
        }
    }
}

但是,对于函数对象,比较运算符 (==) 似乎未定义。我想不出一个简单的方法来解决这个问题。因此,任何想法都非常受欢迎。我只是想从经验中避免某种 id 系统原因,这可能是一项繁琐的管理工作。尤其是当各种函数和成员可以在整个程序中随处添加时。

【问题讨论】:

  • stackoverflow.com/a/18039824/451600 这样的解决方案会起作用吗?
  • 什么时候回调在概念上等同于 unsubscribe()'ing ?请注意,闭包对象(即 lambda 表达式的结果)具有唯一的类型 ...
  • @CaptainGiraffe 甚至假设这是 OP 真正想要的,它仅适用于函数指针和无捕获 lambda(或任何你知道的前期类型)......
  • 你考虑过使用Boost Signals吗?
  • @AdrianKoch "callables" 在 C++ 中没有对它们进行自然相等操作。 std::function 是一个 callable (嗯,可复制的 callables),而不是你认为它是建模的任何东西。其次,您的系统似乎需要将一条消息传递给回调(或某种动态或不安全的强制转换),这似乎不是最理想的。第三,如果你注册了一个回调,然后回调中引用的对象的生命周期结束了怎么办?

标签: c++ dictionary vector std-function


【解决方案1】:

std::function 中没有相等运算符。 可以在旧的tr1::functionN1667找到一个基本原理:

operator== 在 C++ 语言中对于 tr1::function 是无法实现的,因为我们没有可靠的方法来检测给定类型 T 是否在没有用户帮助的情况下平等可比

另请注意,您按值传递 std::function&lt;void(const Message &amp;)&gt;。这意味着您也不能只比较他们的地址(这并不是一个好的解决方案,因为std::function 很容易复制)。

解决方案 1:

使用一些用户提供的 key 和 std::function 作为value 并将它们存储在map 而不是vector 中。

std::map<std::string, std::map<std::string, Callback>> callbackDictionary;
. . .
void Messenger::Subscribe(Message::Type type, const std::string& cbID, Callback cb);

void Messenger::UnSubscribe(const std::string& cbID);

解决方案 2:

使用weak_ptr 来跟踪回调。

std::map<std::string, std::vector<std::weak_ptr<Callback>> callbackDictionary;
. . .
void Messenger::Subscribe(Message::Type type, std::shared_ptr<Callback> cb);

根本不需要UnSubscribe!只要weak_ptr 变成nullptr,您就可以自动取消订阅回调。一个先决条件是 listener 必须通过 shared_ptr 保持回调活动。

【讨论】:

  • 我真的很喜欢第二个选项。事实上,我非常喜欢它,以至于我在其他地方也使用了这种共享的弱指针方法。一个这样的地方描述在:stackoverflow.com/questions/47317531/…。现在的问题是我不太确定这是否仍然是一个好的编程实践。因此,如果您能快速浏览一下,我将不胜感激。
【解决方案2】:

std::function 存储可复制的可调用对象。它不要求它的内容是 == 可比较的,并且 lambdas 不是 == 可比较的。

您可以提取存储的可调用对象的 typeid,如果它们不匹配则假定为 false,添加类型擦除功能,让您可以在各种类型上存储 == 并在类型相等时分派,但随后您不支持 lambda,因为 lambda 不支持 ==

如果你说“没有 lambdas”,你可以这样做,或者如果你说“没有绑定状态的 lambdas”。我稍后会讨论这种可能性,但首先我建议您尝试一下:

using std::shared_ptr<void> token;

template<class...Message>
struct broadcaster {
  using listener = std::function<void(Message...)>;
  using splistener = std::shared_ptr<listener>;
  using wplistener = std::weak_ptr<listener>;

  token listen( listener f ) {
    auto sf = std::make_shared<listener>(std::move(f));
    listeners.push_back(sf); // as weak ptr
    return sf; // as token
  }
  std::size_t operator()( Message... message ) const {
    // remove stale targets:
    targets.erase( std::remove_if( begin(targets), end(targets),
      [](auto&& ptr) { return !ptr.lock(); }
    ), end(targets) );
    auto tmp = targets; // copy, for reentrancy
    for (auto wf : tmp) {
      if(auto sf = wf.lock()) {
        sf( message... );
      }
    }
  }
private:
  mutable std::vector<wplistener> targets;
};

在客户端中,跟踪您正在收听的令牌。当客户端对象消失时,它会自动从它正在收听的每个广播器中注销。只需使用 std::vector&lt;token&gt; 并将您的令牌塞入其中即可。

如果您有更复杂的逻辑,不应该将侦听绑定到侦听器的生命周期,那么您必须单独存储所述令牌。

这假设广播发生的频率与注册/注销的频率大致相同,或者更多。如果广播极其罕见并且注册/注销非常普遍(比如一百万次更常见),那么指向监听器的弱指针就会建立起来。您可以在listen 中添加测试以定期清理陈旧的侦听器。


现在,我们可以做一个“没有绑定状态的 lambdas”。然后我们可以单独绑定状态,在里面输入erase==操作,bob就是你的大叔了。

state( some_state... )
([](some_state&&...){
  return [&](some_args...){
  /* code */
});

像上面这样的构造可以让你返回一个函数对象,它的行为很像 lambda,但上面有一个合理的 == 操作。

template<class F, class...Args>
struct bound_state {
  std::tuple<Args...> state;
  F f;
  friend bool operator==( bound_state const& lhs, bound_state const& rhs ) {
    return lhs.state==rhs.state;
  }
  template<class...Ts>
  decltype(auto) operator()(Ts&&...ts){
    return std::apply(f, state)( std::forward<Ts>(ts)... );
  }
};
template<class...Args>
auto state( Args&&... args ) {
  auto tup = std::make_tuple( std::forward<Args>(args)... );
  return [tup=std::move(tup)](auto&& f) {
    return bound_state<std::decay_t<decltype(f)>, std::decay_t<Args>...>{
      tup, decltype(f)(f)
    };
  };
}

或类似的。

接下来我们创建std::function 的派生类型。当从一个类型构造时,它为它存储一个类型擦除的==(到全局或本地位置)(来自一对std::functions)。

它会覆盖 == 以首先检查 typeid 是否相同,如果相同,它会在两个元素上调用类型擦除的 ==

template<class Sig>
struct func_with_equal : std::function<Sig> {
  using Base = std::function<Sig>;
  using Base::operator();
  using equality = bool( func_with_equal const&, func_with_equal const& );      
  equality* pequal = nullptr;

  template<class F>
  static equality* erase_equality() {
    return [](func_with_equal const& lhs, func_with_equal const&rhs)->bool {
      assert( lhs.target_type() == rhs.target_type() );
      assert( lhs.target_type() == typeid(F) );
      return *lhs.target<F>() == *rhs.target<F>();
    };
  }
  // on construction, store `erase_equality<F>()` into `pequal`.

  friend bool operator==( func_with_equal const& lhs, func_with_equal const& rhs ) {
    if (!lhs && !rhs) return true;
    if (!lhs || !rhs) return false;
    if (lhs.target_type() != rhs.target_type()) return false;
    return lhs.pequal( lhs, rhs );
  }
};

这已经完成了一半,但我希望你明白这一点。它复杂且令人费解,并且在您注册回调的每一点都需要额外的工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-15
    • 1970-01-01
    • 2018-11-12
    • 1970-01-01
    • 2014-05-05
    • 1970-01-01
    相关资源
    最近更新 更多