Reputation: 63
In our codebase, we have a plethora of cases like the following:
auto f = std::make_shared<Foo>();
auto b = std::make_shared<Bar>();
//void Foo::DoStuff( std::shared_ptr<Foo>, int )
auto func = std::bind( &Foo::DoStuff, f, std::_1 );
b->RegisterFunc( func );
Obviously, pocketing the shared_ptr
in a stored callback is generally a bad idea, as it oft causes f
to never be destructed. Normally, you can just pass in a weak_ptr
and check whether it is still valid on execution.
Unfortunately, DoStuff()
modifies internal state of the Foo
object, and is a member function, so it requires the pointer to be valid to avoid crashes. Which causes circular references, and requires manual intervention like calling Foo::Shutdown()
or something to remove them.
Is there a way to work around the circular references storing callbacks of member functions?
Note: For the purposes of the question, I am requiring the bound function to be a non-static member function. It is too ubiquitous in our code to justify completely reworking every example, even if it is clearly a design oversight.
Upvotes: 0
Views: 408
Reputation: 3433
As you say, you have to use weak_ptr
and check that it's valid. weak_ptr
can be used to create valid pointers to the object, so you can still call DoStuff()
on pointers obtained from a weak_ptr
. It's not clear to me why you think weak_ptr
will not work.
Here's your example, but now it's showing a potential circular reference that's avoided with use of weak_ptr
#include <memory>
#include <functional>
#include <vector>
#include <cassert>
struct Foo {
int x = 0;
using callback_t = std::function<void()>;
std::vector<callback_t> callbacks;
void DoStuff() { x = 1; } //mutate some state
void RegisterFunc(callback_t callback) { callbacks.push_back(std::move(callback)); }
};
using Bar = Foo;
template <class T>
auto wrap_shared_ptr_callback(const std::shared_ptr<T>& p, void(T::*f)()) {
return [p = std::weak_ptr{ p }, f]() {
auto ptr = p.lock();
if (ptr) (*ptr.*f)();
};
}
void create_circular_reference(std::shared_ptr<Foo> f, std::shared_ptr<Bar> b) {
auto f_callback = wrap_shared_ptr_callback(f, &Foo::DoStuff);
b->RegisterFunc( f_callback );
auto b_callback = wrap_shared_ptr_callback(b, &Bar::DoStuff);
f->RegisterFunc( b_callback );
// test callbacks
b->callbacks.front()();
f->callbacks.front()();
}
int main() {
auto f = std::make_shared<Foo>();
auto b = std::make_shared<Bar>();
// keep weak ptrs so we can check they've been destroyed correctly
auto f_w = std::weak_ptr{ f };
auto b_w = std::weak_ptr{ b };
create_circular_reference(std::move(f), std::move(b));
assert(f_w.expired());
assert(b_w.expired());
}
Upvotes: 1