Using std::function and std::bind to store callback and handle object deletion.
Template setFunction
so that you can accept pointer-to-member-of-derived, and don't have to write 12 overloads for the combinations of cv/ref qualifiers.
template<class D, class D2, class F>
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) {
// optionally static_assert that D2 is a base of D.
m_function.first = sp;
m_function.second = std::bind(member, sp.get(), std::placeholders::_1);
}
Obviously you need to make sure you lock()
m_function.first
before calling m_function.second
.
Alternatively, just use a lambda that captures both the weak_ptr
and the member function pointer:
std::function<void(double)> m_function;
template<class D, class D2, class F>
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) {
std::weak_ptr<D> wp = sp;
m_function = [wp, member](double d) {
if(auto sp = wp.lock()){
((*sp).*member)(d);
}
else {
// handle pointer no longer valid case.
}
};
}
I like decoupling my listener/broadcaster from the implementation of the listener.
This means I cannot place requirements on the listener. It cannot require the listener be allocated in a particular way.
The easiest method I have found is to have the broadcaster return a token whose lifetime determines the lifetime of the connection.
using token = std::shared_ptr<void>;
template<class...Args>
struct broadcaster {
using target = std::function<void(Args...)>;
using wp_target = std::weak_ptr<target>;
using sp_target = std::shared_ptr<target>;
static sp_target wrap_target( target t ) {
return std::make_shared<target>(std::move(t));
};
token start_to_listen( target f ) {
auto t = wrap_target(std::move(f));
targets.push_back(t);
return t;
}
void broadcast( Args... args ) {
targets.erase(
std::remove_if( targets.begin(), targets.end(),
[&]( wp_target t )->bool { return t.expired(); }
),
targets.end()
);
auto targets_copy = targets; // in case targets is modified by listeners
for (auto wp : targets_copy) {
if (auto sp = wp.lock()) {
(*sp)(args...);
}
}
}
std::vector<wp_target> targets;
};
this forces people who register listeners to keep std::shared_ptr<void>
around.
We can even make it fancier, where the destruction of the last shared_ptr<void>
actually removes the listener from the list immediately. But the above lazy deregistration seems to work reasonably well in my experience, and it is relatively easy to make it multi-thread friendly. (one serious problem is what happens when a broadcast event removes or adds things to the list of listeners: adapting the above for it to work is nice and easy with the rule that listeners added when broadcasting do not get the broadcast, and listeners removed during broadcasting do not get the broadcast. Listeners removed concurrently during broadcast can get the broadcast in most of my implementations... That gets expensive to avoid.)
We could instead decouple it differently. The listener could pass a std::function
and a std::weak_ptr
separately to the broadcaster, who stores both and only calls the std::function
if the std::weak_ptr
is valid.