使用 std::function 作为 class-member 创建回调

Creating a callback with std::function as class-member

我借助纯虚函数设计了一个简单的callback-keyListener-"Interface"。我还使用 shared_ptr 来表达所有权,并确保监听器在处理程序中始终可用。这就像一个魅力,但现在我想在 std::function 的帮助下实现 相同的功能 ,因为有了 std::function 我可以使用 lambdas/functors 而且我不需要从某些 "interface"-类.

派生

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

  1. 为什么这个例子仍然有效,尽管侦听器超出范围? (看来,我们正在使用 copy 监听器而不是 origin 监听器?)

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

示例1:使用虚函数

#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 处于何种状态;在你的情况下,没有状态,没有状态的副本都是一样的。

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

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

这里我们存储一个指向可调用状态的引用或指针。这会被复制,并且在调用时会发生正确的操作。

我与听众的问题是双寿命问题;仅当广播者和接收者都存在时,监听器 link 才有效。

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

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;
    {
      auto l = lock();
      targets.erase(
        std::remove_if( begin(targets), end(targets),
          [](auto&& ptr){ return !(bool)ptr; }
        ),
        end(targets)
      );
      tmp = targets;
    }
    for (auto&& f:tmp) {
      if (f) f(msg...);
    }
  }
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
}