我发现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类通过使用boost :: bind将shared_ptr连接到ActualWorker信号来保持活动。
当worker完成时,将调用Command中的处理程序。现在,因为我希望销毁Command对象,所以我可以断开与信号的连接,如上面的代码所示。问题是实际的插槽对象在断开连接时不会被删除,它只被标记为无效,然后在以后删除。这反过来似乎取决于再次发射的信号,这在我的情况下没有做到,导致插槽永不过期。因此boost :: bind对象永远不会超出范围,将shared_ptr保存到我永远不会被删除的对象。
我可以通过使用this指针而不是shared_ptr进行绑定来解决这个问题,然后使用成员shared_ptr保持我的对象处于活动状态,然后我在处理函数中释放它,但这有点让设计感觉有点过于复杂。断开连接时有没有办法强制signal2删除插槽?或者我还能做些什么来简化设计?
感谢任何评论!
答案 0 :(得分:4)
boost::signals2
会在连接/调用期间清理插槽。
因此,如果所有插槽都与信号断开连接,则第二次调用该信号将不会调用任何信号,但它应该清理插槽。
要回答您的评论,是的,如果连接了其他插槽,则再次调用该信号是不安全的,因为它们将再次被调用。在这种情况下,我建议你走另一条路并连接一个虚拟插槽,然后在调用“真实”插槽时断开它。连接另一个插槽将清除过时连接,因此应释放插槽。
请确保您没有保留任何需要在虚拟插槽中释放的引用,或者您回到起始位置。
答案 1 :(得分:2)
这是boost :: signals2令人难以置信的恼人方面。
我解决它的方法是将信号存储在scoped_ptr中,当我想强制断开所有插槽时,我删除了信号。这仅适用于您要强制断开与信号的所有连接的情况。
答案 2 :(得分:1)
对于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对象外面来的。当您构造Command以执行它时,您应该能够说:
cmd->Execute();
delete cmd;
或类似。
答案 3 :(得分:1)
我最终做了我自己的(子集)信号实现,主要要求是应该通过调用connection :: disconnect()来销毁一个槽。
该实现沿着信号的路线存储,该信号存储从时隙实现指针到用于时隙实现而不是列表/向量的shared_ptr的映射中的所有时隙,从而允许快速访问各个时隙而不必迭代所有时隙。在我的情况下,插槽实现基本上是一个boost :: function。
Connections有一个weak_ptr到信号的内部实现类,weak_ptr到槽实现类型,允许信号超出范围,并使用槽指针作为信号映射的键,以及指示关于连接是否仍处于活动状态(不能使用原始指针,因为它可能会被重用)。
当调用disconnect时,这两个弱指针都转换为shared_ptrs,如果这两个指针都成功,则要求信号实现断开指针给出的槽。这是通过从地图上简单地擦除它来完成的。
映射受互斥锁保护,允许多线程使用。为了防止死锁,在调用插槽时不保持互斥锁,但是这意味着在信号调用之前可以从不同的线程断开插槽。常规的boost :: signals2也是这种情况,在这两种情况下,即使在断开连接之后,也需要能够处理来自信号的回调。
为了简化信号触发时的代码,我强制在此期间断开所有插槽。这与boost :: signals2不同,它在调用它们之前执行槽列表的副本,以便在触发信号时处理断开连接/连接。
以上情况适用于我的场景,其中感兴趣的信号很少被触发(在这种情况下只有一次)但是有很多短暂的连接,否则会占用大量内存,即使使用问题中概述的技巧。
对于其他场景,我已经能够仅使用boost :: function替换信号的使用(因此要求只能有一个连接),或者仅仅通过坚持使用问题解决方法听众本身管理其一生。
答案 4 :(得分:1)
我偶然发现了同样的问题,我真的很想念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行代码,并向cleanup_connections()
添加了一个公开signal
方法。我的信号包装器在断开连接方法结束时调用清理。这种方法解决了我的问题,到目前为止我没有遇到任何性能问题。
编辑:以下是我对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));