【问题标题】:Force deletion of slot in boost::signals2强制删除 boost::signals2 中的插槽
【发布时间】:2010-01-12 13:44:23
【问题描述】:

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

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

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.

Command 类通过为自身获取一个 shared_ptr 来保持自身活动,该 shared_ptr 使用 boost::bind 绑定到 ActualWorker 信号。

当工作人员完成时,会调用 Command 中的处理程序。现在,由于我希望销毁 Command 对象,因此我断开了与信号的连接,如上面的代码所示。问题是实际的槽对象在断开连接时并没有被删除,它只是被标记为无效,然后在以后删除。这反过来似乎取决于再次触发的信号,在我的情况下它没有这样做,导致插槽永不过期。因此 boost::bind 对象永远不会超出范围,它会为我的对象保存一个永远不会被删除的 shared_ptr。

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

感谢任何cmets!

【问题讨论】:

    标签: c++ boost boost-signals2


    【解决方案1】:

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

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

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

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

    【讨论】:

    • 这正是我在问题中写的,我想避免。如果问题文本没有正确传达这一点,我很抱歉。无论如何,信号不会再次被调用 - 我假设你不建议第二次调用它作为删除对象的手段?
    • 这正是我的建议,抱歉措辞奇怪。 boost::signals2 在调用期间“垃圾收集”断开连接的插槽,因此如果您在断开连接后再次调用它,它应该删除您的对象。
    • 抱歉回复晚了,好久没联系了。但是,您的建议要求我知道没有其他插槽连接到信号,否则这些将被多次调用,这可能安全也可能不安全。
    【解决方案2】:

    这是 boost::signals2 令人难以置信的烦人之处。

    我解决它的方法是将信号存储在 scoped_ptr 中,当我想强制断开所有插槽时,我删除了信号。这仅适用于您想要强制断开与信号的所有连接的情况。

    【讨论】:

      【解决方案3】:

      scoped_connection 的行为是否更严格?

      所以,而不是:

      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(),因为 scoped_connection 会为你处理它。这更像是“简化设计”,而不是真正解决您的问题。但这可能意味着您可以先Execute(),然后立即~Command()(或delete shared_ptr)。

      希望对您有所帮助。

      编辑:Execute() 然后立即~Command() 我显然是指从您的 Command 对象之外。当您构造命令来执行它时,您应该可以说:

      cmd->Execute();
      delete cmd;
      

      或类似的。

      【讨论】:

      • 在执行函数结束时断开连接完全违背了连接的目的——即当异步工作完成时我不会得到回调。所以你的建议并不可行。
      【解决方案4】:

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

      该实现遵循将所有插槽存储在映射中的信号线,从插槽实现指针到用于插槽实现而不是列表/向量的 shared_ptr,从而快速访问各个插槽,而无需遍历所有插槽.在我的情况下,插槽实现基本上是一个 boost::function。

      连接有一个指向信号内部实现类的weak_ptr和一个指向槽实现类型的weak_ptr,以允许信号超出范围并使用槽指针作为信号映射的键以及指示关于连接是否仍然处于活动状态(不能使用原始指针,因为它可能会被重用)。

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

      映射受互斥体保护以允许多线程使用。为了防止死锁,在调用槽时不会保持互斥锁,但这意味着槽可能会在被信号调用之前与不同的线程断开连接。常规 boost::signals2 也是如此,在这两种情况下,即使在信号断开连接后,也需要能够处理来自信号的回调。

      为了简化触发信号时的代码,我强制在此期间断开所有插槽。这与 boost::signals2 不同,boost::signals2 在调用它们之前会复制插槽列表,以便在触发信号时处理断开/连接。

      以上方法适用于我的场景,其中感兴趣的信号很少被触发(在这种情况下只有一次),但是有很多短暂的连接,否则即使使用问题中概述的技巧。

      对于其他情况,我已经能够用一个 boost::function 替换信号的使用(因此要求只能有一个连接),或者只是坚持解决问题的解决方法监听器自己管理它的生命周期。

      【讨论】:

        【解决方案5】:

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

        在我的场景中,我正在卸载一些插件 dll,并且我必须确保没有悬挂对象(插槽)引用存在于卸载的 dll 中的代码(vftables 或其他任何东西)。由于延迟删除的东西,简单地断开插槽是行不通的。

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

        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 行代码,并在 signal 中添加了一个公共的 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));
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-02-22
          • 2012-04-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多