I want to implement a manager that stores callbacks to member functions of polymorphic classes using C++11. The issue is that I am not sure how to handle the case where the object that the member belongs to gets deleted or should be deleted and I want to make the interface as simple as possible.
So I thought of the following: Store a std::weak_ptr
to the object as well as a std::function
to the member.
The following seems to work:
class MyBase {
public:
MyBase() {}
virtual ~MyBase() {}
};
//--------------------------------------------------
class MyClass : public MyBase {
public:
MyClass() : MyBase() {}
void myDouble(double val) const { std::cout << "Value is: " << val << std::endl; }
};
//--------------------------------------------------
Class Manager {
public:
void setFunction(std::weak_ptr<MyBase> base, std::function<void(double)> func) {
m_function.first = base;
m_function.second = func;
}
private:
std::pair<std::weak_ptr<MyBase>, std::function<void(double)>> m_function;
};
To use this:
Manager db;
std::shared_ptr<MyClass> myClass = std::make_shared<MyClass>();
db.setFunction(myClass, std::bind(&MyClass::myDouble, myClass, std::placeholders::_1));
Now I want to hide the std::bind
part from the user, so that he only needs to call:
db.setFunction(myClass, &MyClass::myDouble);
So I want to get almost the following working in my manager function:
void setFunc2(std::weak_ptr<MyBase> base, std::function<void(double)> func) {
m_function.first = base;
m_function.second = std::bind(func, base, std::placeholders::_1);
}
But the above gives errors:
error: no match for 'operator=' (operand types are 'std::function<void(double)>' and
'std::_Bind_helper<false, std::function<void(double)>&, std::weak_ptr<MyBase>&, const std::_Placeholder<1>&>::type {aka std::_Bind<std::function<void(double)>(std::weak_ptr<MyBase>, std::_Placeholder<1>)>}')
m_function.second = std::bind(func, base, std::placeholders::_1);
Is there a better way to do this, or perhaps a way to get this working?
Something interesting that I notice. If I use the std::shared_ptr
the use_count()
gets incremented with the call to std::bind
in the original code. Thus I can not manually reset/destroy the object unless I unset the member on my manager. Where is this behaviour documented, I normally use cppreference?
I have looked at the following question but can't seem to get it working for my problem: How can I use polymorphism with std::function?
std::bind is for partial function application. That is, suppose you have a function object f which takes 3 arguments: f(a,b,c); You want a new function object which only takes two arguments, defined as: g(a,b) := f(a, 4, b);
Internally, std::bind() detects that a pointer to a member function is passed and most likely turns it into a callable objects, e.g., by use std::mem_fn() with its first argument.
boost::bind is a generalization of the standard functions std::bind1st and std::bind2nd. It supports arbitrary function objects, functions, function pointers, and member function pointers, and is able to bind any argument to a specific value or route input arguments into arbitrary positions.
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.
I like Yakk's approach. Here's an updated version that fixes a few compile issues (e.g. cannot name function 'register'). It also adds a rm_callback method for clients to easily remove their registration without forcing their registration token to go out of scope or knowing the internals. I didn't like scanning the list every time an event was broadcast so I added a deleter on the shared pointer which does the cleanup task. All new bugs introduced or inefficiencies are mine. The alert reader should be aware of threading issues when modifying the list while broadcasting...
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>;
token add_callback(target f) {
sp_target t(new target(std::move(f)), [&](target*obj) { delete obj; cleanup(); });
targets.push_back(t);
return t;
}
static void rm_callback(token& t)
{
t.reset();
}
void cleanup()
{
targets.erase(
std::remove_if(targets.begin(), targets.end(),
[](wp_target t) { return t.expired(); }
),
targets.end()
);
}
void broadcast(Args... args) {
for (auto wp : targets) {
if (auto sp = wp.lock()) {
(*sp)(args...);
}
}
}
std::vector<wp_target> targets;
};
// declare event taking a string arg
broadcaster<std::string> myEvent;
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.
}
};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With