【问题标题】:std::function as callback, is unregistration possible?std::function 作为回调,是否可以注销?
【发布时间】:2013-04-10 03:02:05
【问题描述】:

这个问题完全是关于std::function 而不是boost::function。有关详细信息,请参阅此问题底部的 更新 部分,尤其是关于无法根据 C++11 标准比较非空 std::function 对象的部分。


C++11 std::function 类模板非常适合维护回调集合。例如,可以将它们存储在vector 中,并在需要时调用它们。然而,维护这些对象并允许注销似乎是不可能的。

让我具体一点,想象一下这个类:

class Invoker
{
public:
  void Register(std::function<void()> f);
  void Unregister(std::function<void()> f);

  void InvokeAll();

private:
  // Some container that holds the function objects passed to Register()
};

示例使用场景:

void foo()
{
}

int main()
{
  std::function<void()> f1{foo};
  std::function<void()> f2{[] {std::cout << "Hello\n";} };

  Invoker inv;

  // The easy part

  // Register callbacks
  inv.Register(f1);
  inv.Register(f2);

  // Invoke them
  inv.InvokeAll();

  // The seemingly impossible part. How can Unregister() be implemented to actually
  // locate the correct object to unregister (i.e., remove from its container)?
  inv.Unregister(f2);
  inv.Unregister(f1);
}

Register() 函数是如何实现的相当清楚。但是,如何实现Unregister()。假设保存函数对象的容器是 vector&lt;std::function&lt;void()&gt;&gt; 。您如何找到传递给Unregister() 调用的特定函数对象? std::function 确实提供了重载的operator==,但它只测试空函数对象(即,它不能用于比较两个非空函数对象以查看它们是否都引用相同的实际调用)。

我会很感激任何想法。

更新:

目前的想法主要包括添加一个与每个std::function 对象关联的cookie,该对象可用于注销它。我希望有一些与std::function 对象本身无关的东西。此外,std::functionboost::function 之间似乎存在很多混淆。这个问题完全是关于std::function 对象,而不是 boost::function 对象。

此外,您不能比较两个非空 std::function 对象的相等性。根据标准,他们总是会比较不相等。因此,在此问题的上下文中,cmets 中指向执行此操作(并使用 boost::function 对象引导)的解决方案的链接显然是错误的。

【问题讨论】:

标签: c++ c++11


【解决方案1】:

由于您无法测试容器中的元素身份,因此最好使用一个容器(例如std::list),其迭代器在容器被修改时不会失效,并将迭代器返回给注册调用者,这些调用者可以用于注销。

如果您真的想使用vector(或deque),您可以在添加回调时将整数索引返回到vector/deque。这种策略自然会要求您确保以这种方式使用索引来识别函数在序列中的位置。如果回调和/或注销很少见,这可能仅仅意味着不重复使用点。或者,您可以实现一个空闲列表来重用空槽。或者,仅从序列末尾回收空槽,并保持一个基本索引偏移量,当槽从开头回收时增加。

如果您的回调访问模式不需要随机访问遍历,则将回调存储在 std::list 中并使用原始迭代器取消注册对我来说似乎是最简单的。

【讨论】:

  • 做了17年的C++程序员,终于知道自己用std::list的理由了!
【解决方案2】:

我对此有一个想法。

将回调存储为std::weak_ptr&lt;std::function&lt;void(argtype1, argtype1)&gt;&gt;。然后调用者负责保持对应的std::shared_ptr处于活动状态,调用者取消注册回调所要做的就是将所有活动的std::shared_ptrs销毁到回调函数中。

调用回调时,代码必须小心检查它正在使用的std::weak_ptr&lt;&gt;s 上的锁定失败。当它遇到这些时,它可以将它们从注册的回调容器中删除。

请注意,这并不能提供完整的线程安全性,因为回调调用者可以锁定 std::weak_ptr 并创建一个临时新激活的回调函数 std::shared_ptr,在调用者的 std::shared_ptr 超出范围后可以保持活动状态.

【讨论】:

    猜你喜欢
    • 2020-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-26
    • 1970-01-01
    • 2017-08-06
    • 1970-01-01
    相关资源
    最近更新 更多