使用std :: function作为类成员

时间:2017-12-17 13:48:35

标签: c++ c++11 copy std-function ownership

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

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

  1. 为什么这个例子仍然有用,虽然听众超出了范围? (看来,我们正在使用侦听器的副本而不是 origin 侦听器?)

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

  3. 示例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();
    }
    

1 个答案:

答案 0 :(得分:0)

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

这意味着它会复制你输入的内容。

在C ++中,可以复制或移动的东西应该与原始版本很相似。你正在复制或移动的东西可以带有引用或指向一个extrenal资源的指针,一切都应该正常工作。

如何正确地适应价值语义取决于您在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;});
  }

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

我与听众的问题是doulbe终身问题;只要广播者和接收者都存在,监听器链接才有效。

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

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
}