代码之家  ›  专栏  ›  技术社区  ›  villintehaspam

强制删除boost::signals2中的插槽

  •  12
  • villintehaspam  · 技术社区  · 15 年前

    我发现boost::signals2使用了一种延迟删除连接插槽的方法,这使得很难将连接用作管理对象生命周期的工具。我正在寻找一种方法,在断开连接时强制直接删除插槽。任何关于如何通过设计不同的代码来解决这个问题的想法,我们都将不胜感激!

    这是我的场景:我有一个命令类负责异步执行一些需要时间的操作,看起来像这样(简化):

    class ActualWorker {
    public:
        boost::signals2<void ()> OnWorkComplete;
    };
    
    class Command : boost::enable_shared_from_this<Command> {
    public:
        ...
    
        void Execute() {
            m_WorkerConnection = m_MyWorker.OnWorkDone.connect(boost::bind(&Command::Handle_OnWorkComplete, shared_from_this());
    
            // launch asynchronous work here and return
        }
    
        boost::signals2<void ()> OnComplete;
    
    private:
        void Handle_OnWorkComplete() {
            // get a shared_ptr to ourselves to make sure that we live through
            // this function but don't keep ourselves alive if an exception occurs.
            shared_ptr<Command> me = shared_from_this();
    
            // Disconnect from the signal, ideally deleting the slot object
            m_WorkerConnection.disconnect();
    
            OnComplete();
    
            // the shared_ptr now goes out of scope, ideally deleting this
        }
    
        ActualWorker m_MyWorker;
        boost::signals2::connection m_WorkerConnection;
    };
    

    该类的调用方式如下:

    ...
    boost::shared_ptr<Command> cmd(new Command);
    cmd->OnComplete.connect( foo );
    cmd->Execute();
    // now go do something else, forget all about the cmd variable etcetera.
    

    命令类通过使用boost::bind将共享的_ptr绑定到实际的worker信号,从而使自己保持活动状态。

    当工作程序完成时,将调用命令中的处理程序。现在,因为我想销毁命令对象,所以我断开了与信号的连接,正如上面代码中所示。问题是实际插槽对象在断开连接时不会被删除,它只会被标记为无效,然后在以后删除。这又似乎取决于再次发射的信号,而在我的例子中,它没有这样做,导致插槽永远不会过期。因此,boost::bind对象永远不会超出范围,它持有一个与我的对象共享的、永远不会被删除的\u ptr。

    我可以通过使用this指针而不是shared_ptr绑定来解决这个问题,然后使用成员shared_ptr使我的对象保持活动状态,然后在handler函数中释放该成员,但这会让设计感觉有点过于复杂。断开连接时是否有办法强制信号2删除插槽?或者我还能做些什么来简化设计?

    任何意见都将不胜感激!

    5 回复  |  直到 15 年前
        1
  •  3
  •   Dan Berindei    14 年前

    boost::signals2 在连接/调用期间清理插槽。

    因此,如果所有插槽都与信号断开连接,第二次调用信号不会调用任何内容,但它应该清理插槽。

    回答您的评论,是的,如果连接了其他插槽,再次调用信号是不安全的,因为它们将再次被调用。在这种情况下,我建议您反过来连接一个虚拟插槽,然后在调用“真实”插槽时断开它。连接另一个插槽将清除陈旧的连接,因此您的插槽应该被释放。

    只需确保不要在虚拟插槽中保留任何需要释放的引用,否则就回到了开始的位置。

        2
  •  2
  •   Kranar    14 年前

        3
  •  1
  •   user244343 user244343    15 年前

    对于作用域_连接,行为是否更加严格?

    因此,不是:

    void Execute() {
        m_WorkerConnection = m_MyWorker.OnWorkDone.connect(boost::bind
            (&Command::Handle_OnWorkComplete, shared_from_this());
    
        // launch asynchronous work here and return
    }
    
    ...
    
    boost::signals2::connection m_WorkerConnection;
    

    void Execute() {
        boost::signals2::scoped_connection m_WorkerConnection
            (m_MyWorker.OnWorkDone.connect(boost::bind
            (&Command::Handle_OnWorkComplete, shared_from_this()));
    
        // launch asynchronous work here and return
    }   // connection falls out of scope
    

    (复制品由 boost::signals2::connection )

    我没有使用过任何类型的信号,所以这更像是猜测,而不是跟踪 Execute() 你不需要这么做 disconnect() ,因为作用域_连接为您处理它。这更像是“简化设计”,而不是实际解决问题。但这可能意味着你可以 执行() 然后马上 ~Command() (或 delete 共享资源(ptr)。

    执行() 然后马上 ~Command() 我的意思显然是从你的命令对象之外。当您构造命令来执行它时,您应该能够说:

    cmd->Execute();
    delete cmd;
    

        4
  •  1
  •   villintehaspam    14 年前

    我最终完成了自己的信号(子集)实现,主要要求是通过调用connection::disconnect()销毁插槽。

    该实现沿着将所有插槽存储在从插槽实现指针到插槽实现的共享ptr(而不是列表/向量)的映射中的信号线进行,从而提供对单个插槽的快速访问,而无需迭代所有插槽。在我的例子中,插槽实现基本上是一个boost::函数。

    连接对信号的内部实现类具有弱\u ptr,对插槽实现类型具有弱\u ptr,以允许信号超出范围,并将插槽指针用作信号映射中的键,以及指示连接是否仍处于活动状态(不能使用原始指针,因为这可能是错误的)重复使用)。

    调用disconnect时,这两个弱指针都将转换为共享_ptr,如果这两个都成功,则要求信号实现断开指针给定的插槽。这是通过简单地从地图上删除它来完成的。

    上述方法适用于我的场景,在我的场景中,感兴趣的信号很少被触发(在这种情况下只触发一次),但存在大量短期连接,否则即使使用问题中概述的技巧,也会消耗大量内存。

    对于其他场景,我已经能够用boost::函数(因此要求只能有一个连接)来代替信号的使用,或者只是通过坚持解决侦听器自身管理其生命周期的问题。

        5
  •  1
  •   eel76    11 年前

    我偶然发现了同样的问题,我真的错过了API中的某种显式清理。

    在我的场景中,我正在卸载一些插件dll,我必须确保在卸载的dll中没有引用代码(vftable或其他)的悬空对象(插槽)。由于延迟删除的原因,简单地断开插槽是不起作用的。

    我的第一个解决方案是一个信号包装器,它稍微调整了代码:

    template <typename Signature>
    struct MySignal
    {
      // ...
    
      template <typename Slot>
      void disconnect (Slot&& s)
      {
        mPrivate.disconnect (forward (s));
        // connect/disconnect dummy slot to force cleanup of s
        mPrivate.connect (&MySignal::foo);
        mPrivate.disconnect (&MySignal::foo);
      }
    
    private:
      // dummy slot function with matching signature
      // ... foo (...)
    
    private:
      ::boost::signals2::signal<Signature> mPrivate;
    };
    

    不幸的是,这不起作用,因为 connect() 只有 一些 清理。它不能保证清理所有未连接的插槽。另一方面,信号调用会进行完全清理,但伪调用也是一种不可接受的行为更改(正如其他人已经提到的)。

    在没有其他选择的情况下,我最终修补了原作 signal 阶级( 希望有一个内置的解决方案。这个补丁是我最后的选择)。我的补丁大约有10行代码,并添加了一个公共 cleanup_connections() 方法

    这是我的boost 1.5.3补丁

    Index: signals2/detail/signal_template.hpp
    ===================================================================
    --- signals2/detail/signal_template.hpp
    +++ signals2/detail/signal_template.hpp
    @@ -220,6 +220,15 @@
               typedef mpl::bool_<(is_convertible<T, group_type>::value)> is_group;
               do_disconnect(slot, is_group());
             }
    +        void cleanup_connections () const
    +        {
    +          unique_lock<mutex_type> list_lock(_mutex);
    +          if(_shared_state.unique() == false)
    +          {
    +            _shared_state.reset(new invocation_state(*_shared_state, _shared_state->connection_bodies()));
    +          }
    +          nolock_cleanup_connections_from(false, _shared_state->connection_bodies().begin());
    +        }
             // emit signal
             result_type operator ()(BOOST_SIGNALS2_SIGNATURE_FULL_ARGS(BOOST_SIGNALS2_NUM_ARGS))
             {
    @@ -690,6 +699,10 @@
           {
             (*_pimpl).disconnect(slot);
           }
    +      void cleanup_connections ()
    +      {
    +        (*_pimpl).cleanup_connections();
    +      }
           result_type operator ()(BOOST_SIGNALS2_SIGNATURE_FULL_ARGS(BOOST_SIGNALS2_NUM_ARGS))
           {
             return (*_pimpl)(BOOST_SIGNALS2_SIGNATURE_ARG_NAMES(BOOST_SIGNALS2_NUM_ARGS));