Search code examples
c++c++11std-functionstdbind

Using std::function and std::bind to store callback and handle object deletion.


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?


Solution

  • 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.
            }
        };
    }