Reputation: 311
I wondered whether the following code is safe with respect to the fact that the signal might be triggered by a different thread:
using IntSignal = boost::signals2::signal<void(int)>;
class Foo
{
public:
Foo(IntSignal& signal)
: some_resource(new int(0))
{
scoped_connection = signal.connect([this](int i) { some_action(i); });
}
~Foo()
{
delete some_resource;
}
private:
void some_action(int i)
{
*some_resource = i;
}
int* some_resource;
boost::signals2::scoped_connection scoped_connection;
}
EDIT: added an imaginary resource, destructor and an implementation for some_action
to make it more clear. With this question I would like to clarify whether my assumption is correct that the signal's slot might be called after Foo
's destructor but before scoped_connection
's destructor. I omitted a mutex protecting some_resource
for brevity, however, it is not relevant for the question.
Although the connection will be dropped when a Foo
instance is destroyed, there might be a tiny gap betwen Foo
's own destructor invocation and the destruction of Foo
's members. This might be even more problematic if resources are being used within some_action
after they have been destructed.
Should I rather use normal connections and disconnect them in Foo
's destructor? And would it be safe to have the scoped_connection
member as last member of the class (that should get destroyed first) and omit any destruction logic?
Upvotes: 1
Views: 846
Reputation: 4575
You are right, there is a possible race if Foo
's destructor is invoked while the signal is running and accessing some_resource
.
A safe solution would be to extend the life of Foo
while the slots are running:
class Foo : public std::enable_shared_from_this<Foo>
{
public:
Foo(IntSignal& signal)
: some_resource(new int(0))
{
// moved connection initialization to a method since weak_from_this will
// be empty inside the constructor. It is initialized only afterwards.
// It also make sense to connect your signal only after object
// is fully initialized
}
void InitConnection()
{
auto weak_self = weak_from_this();
scoped_connection = signal.connect(
[weak_self](int i)
{
if (auto self = weak_self.lock())
{
// we managed to promote weak_self to a shared_ptr, 'this' will be alive
// as long as this scope lives
some_action(i); // safe
}
});
}
~Foo()
{
// will only be invoked after Foo's reference count drops to zero.
// not during the signal is emitted
delete some_resource;
}
private:
void some_action(int i)
{
*some_resource = i;
}
int* some_resource;
boost::signals2::scoped_connection scoped_connection;
}
Notes:
enable_shared_from_this
initializes a weak_ptr
to 'this'. It is a great tool for the situation you described. See more here.Upvotes: 1