繁体   English   中英

使用 std::function 作为类成员创建回调

[英]Creating a callback with std::function as class-member

我在纯虚拟 function 的帮助下设计了一个简单的 callback-keyListener-“接口”。我还使用了 shared_ptr 来表达所有权并确保监听器在处理程序中始终可用。 这就像一个魅力,但现在我想在 std::function 的帮助下实现相同的功能,因为有了 std::function 我能够使用 lambdas/仿函数,我不需要从一些“接口”派生-类。

我尝试在第二个示例中实现 std::function-variant,它似乎可以工作,但我有两个与示例 2 相关的问题:

  1. 为什么这个例子仍然有效,尽管监听器在 scope 之外? (看起来,我们正在使用监听器的副本而不是原始监听器?)

  2. 我如何修改第二个示例,以实现与第一个示例相同的功能(在原始侦听器上工作)? (std::function 的成员指针似乎不起作用,当侦听器在处理程序之前离开 scope 时,我们如何处理这种情况?)

示例 1:使用虚拟 function

#include <memory>

struct KeyListenerInterface
{
    virtual ~KeyListenerInterface(){}
    virtual void keyPressed(int k) = 0;
};

struct KeyListenerA : public KeyListenerInterface
{
    void virtual keyPressed(int k) override {}
};

struct KeyHandler
{
    std::shared_ptr<KeyListenerInterface> m_sptrkeyListener;

    void registerKeyListener(std::shared_ptr<KeyListenerInterface> sptrkeyListener)
    {
        m_sptrkeyListener = sptrkeyListener;
    }

    void pressKey() { m_sptrkeyListener->keyPressed(42); }
};

int main()
{
    KeyHandler oKeyHandler;

    {
        auto sptrKeyListener = std::make_shared<KeyListenerA>();
        oKeyHandler.registerKeyListener(sptrKeyListener);
    }

    oKeyHandler.pressKey();
}

示例 2:使用 std::function

#include <functional>
#include <memory>

struct KeyListenerA
{
    void operator()(int k) {}
};

struct KeyHandler
{
    std::function<void(int)>  m_funcKeyListener;

    void registerKeyListener(const std::function<void(int)> &funcKeyListener)
    {
        m_funcKeyListener = funcKeyListener;
    }

    void pressKey() { m_funcKeyListener(42); }
};

int main()
{
    KeyHandler oKeyHandler;

    {
        KeyListenerA keyListener;
        oKeyHandler.registerKeyListener(keyListener);
    }

    oKeyHandler.pressKey();
}

std::function<Sig>实现值语义回调。

这意味着它会复制您放入其中的内容。

在 C++ 中,可以复制或移动的东西应该和原来的一样。 您正在复制或移动的东西可以带有对外部资源的引用或指针,一切都应该正常工作。

究竟如何适应值语义取决于您在 KeyListener 中想要什么 state 在你的情况下,没有 state,没有 state 的副本都是一样的。

我假设我们想要关心它存储的 state:

struct KeyListenerA {
  int* last_pressed = 0;
  void operator()(int k) {if (last_pressed) *last_pressed = k;}
};

struct KeyHandler {
  std::function<void(int)>  m_funcKeyListener;

  void registerKeyListener(std::function<void(int)> funcKeyListener) {
    m_funcKeyListener = std::move(funcKeyListener);
  }

  void pressKey() { m_funcKeyListener(42); }
};

int main() {
  KeyHandler oKeyHandler;      
  int last_pressed = -1;
  {
    KeyListenerA keyListener{&last_pressed};
    oKeyHandler.registerKeyListener(keyListener);
  }

  oKeyHandler.pressKey();
  std::cout << last_pressed << "\n"; // prints 42
}

要么

  {
    oKeyHandler.registerKeyListener([&last_pressed](int k){last_pressed=k;});
  }

在这里,我们在可调用对象中存储指向 state 的引用或指针。 这会被复制,并且在调用时会发生正确的操作。

我对听众的问题是双寿命问题; 只要广播者和接收者都存在,监听器链接才有效。

为此,我使用这样的东西:

using token = std::shared_ptr<void>;
template<class...Message>
struct broadcaster {
  using reciever = std::function< void(Message...) >;

  token attach( reciever r ) {
    return attach(std::make_shared<reciever>(std::move(r)));
  }
  token attach( std::shared_ptr<reciever> r ) {
    auto l = lock();
    targets.push_back(r);
    return r;
  }
  void operator()( Message... msg ) {
    decltype(targets) tmp;
    {
      // do a pass that filters out expired targets,
      // so we don't leave zombie targets around forever.
      auto l = lock();
      targets.erase(
        std::remove_if( begin(targets), end(targets),
          [](auto&& ptr){ return ptr.expired(); }
        ),
        end(targets)
      );
      tmp = targets; // copy the targets to a local array
    }
    for (auto&& wpf:tmp) {
      auto spf = wpf.lock();
      // If in another thread, someone makes the token invalid
      // while it still exists, we can do an invalid call here:
      if (spf) (*spf)(msg...);
      // (There is no safe way around this issue; to fix it, you
      // have to either restrict which threads invalidation occurs
      // in, or use the shared_ptr `attach` and ensure that final
      // destruction doesn't occur until shared ptr is actually
      // destroyed.  Aliasing constructor may help here.)
    }
  }
private:
  std::mutex m;
  auto lock() { return std::unique_lock<std::mutex>(m); }
  std::vector< std::weak_ptr<reciever> > targets;
};

它将您的代码转换为:

struct KeyHandler {
  broadcaster<int> KeyPressed;
};

int main() {
  KeyHandler oKeyHandler;      
  int last_pressed = -1;
  token listen;
  {
    listen = oKeyHandler.KeyPressed.attach([&last_pressed](int k){last_pressed=k;});
  }

  oKeyHandler.KeyPressed(42);

  std::cout << last_pressed << "\n"; // prints 42
  listen = {}; // detach

  oKeyHandler.KeyPressed(13);
  std::cout << last_pressed << "\n"; // still prints 42
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM