简体   繁体   中英

Binding to a weak_ptr

Is there a way to std::bind to a std::weak_ptr ? I'd like to store a "weak function" callback that automatically "disconnects" when the callee is destroyed.

I know how to create a std::function using a shared_ptr:

std::function<void()> MyClass::GetCallback()
{
    return std::function<void()>(std::bind(&MyClass::CallbackFunc, shared_from_this()));
}

However the returned std::function keeps my object alive forever. So I'd like to bind it to a weak_ptr :

std::function<void()> MyClass::GetCallback()
{
    std::weak_ptr<MyClass> thisWeakPtr(shared_from_this());
    return std::function<void()>(std::bind(&MyClass::CallbackFunc, thisWeakPtr));
}

But that doesn't compile. (std::bind will accept no weak_ptr!) Is there any way to bind to a weak_ptr?

I've found discussions about this (see below), but there seems to be no standard implementation. What is the best solution for storing a "weak function", in particular if Boost is not available?


Discussions / research (all of these use Boost and are not standardized):

std::weak_ptr<MyClass> thisWeakPtr(shared_from_this());
return std::function<void()>(std::bind(&MyClass::CallbackFunc, thisWeakPtr));

You should never do this. Ever.

MyClass::CallbackFunc is a non-static member function of the class MyClass . Being a non-static member function, it must be called with a valid instance of MyClass .

The entire point of weak_ptr is that it isn't necessarily valid. You can detect its validity by transforming it into a shared_ptr and then testing if the pointer is NULL. Since weak_ptr is not guaranteed to be valid at all times, you cannot call a non-static member function with one.

What you're doing is no more valid than:

std::bind(&MyClass::CallbackFunc, nullptr)

It may compile, but it will eventually crash when you try to call it.

Your best bet is to use actual logic, to not call the callback function if the weak_ptr is not valid. bind is not designed to do logic; it just does exactly what you tell it to: call the function. So you need to use a proper lambda:

std::weak_ptr<MyClass> thisWeakPtr(shared_from_this());
return std::function<void()>([thisWeakPtr]()
{
  auto myPtr = thisWeakPtr.lock();
  if(myPtr)
    myPtr->CallbackFunc()
});

I was able to create weak_pointers of std::function and tested it with clang-3.2 (you didn't give any compiler restrictions).

Here's a sample app that creates and tests what I believe you are asking for:

#include <functional>
#include <memory>
#include <iostream>

typedef std::function<void(void)> Func;
typedef std::shared_ptr<Func> SharedFunc;
typedef std::weak_ptr<Func> WeakFunc;


void Execute( Func f ) {
    f();
}


void Execute( SharedFunc sf ) {
    (*sf)();
}


void Execute( WeakFunc wf ) {
    if ( auto f = wf.lock() )
        (*f)();
    else
        std::cout << "Your backing pointer went away, sorry.\n";
}

int main(int, char**) {

    auto f1 = [](){ std::cout << "Func here.\n"; };
    Execute( f1 );

    auto f2 = [](){ std::cout << "SharedFunc here.\n"; };
    SharedFunc sf2( new Func(f2) );
    Execute( sf2 );

    auto f3 = [](){ std::cout << "WeakFunc here.\n"; };
    SharedFunc sf3( new Func(f3) );
    WeakFunc wf3( sf3 );
    Execute( wf3 );

    // Scoped test to make sure that the weak_ptr is really working.
    WeakFunc wf4;
    {
        auto f4 = [](){ std::cout << "You should never see this.\n"; };
        SharedFunc sf4( new Func(f4) );
        wf4 = sf4;
    }
    Execute( wf4 );

    return 0;
}

The output was:

~/projects/stack_overflow> clang++-mp-3.2 --std=c++11 --stdlib=libc++ weak_fun.cpp -o wf && ./wf
Func here.
SharedFunc here.
WeakFunc here.
Your backing pointer went away, sorry.
#include <iostream>
#include <string>
#include <memory>
#include <functional>
using namespace std;

template < typename T > class LockingPtr {
    std :: weak_ptr < T > w;
public:
    typedef shared_ptr < T > result_type;
    LockingPtr ( const std :: shared_ptr < T > & p ) : w ( p ) { }
    std :: shared_ptr < T > lock ( ) const {
        return std :: shared_ptr < T > ( w );
    }
    std :: shared_ptr < T > operator-> ( ) const {
        return lock ( );
    }
    template < typename ... Args > std :: shared_ptr < T > operator( ) ( Args ... ) const {
        return lock ( );
    }
};

template < typename T > LockingPtr < T > make_locking ( const shared_ptr < T > & p ) {
    return p;
}

namespace std {
    template < typename T > struct is_bind_expression < LockingPtr < T > > :
        public true_type { };
}

int main() {
    auto p = make_shared < string > ( "abc" );
    auto f = bind ( & string :: c_str, make_locking ( p ) );
    cout << f ( ) << '\n';
    p.reset ( );
    try {
    cout << f ( ) << '\n';
    } catch ( const exception & e ) {
        cout << e.what ( ) << '\n';
    }
    // your code goes here
    return 0;
}

output:

abc
bad_weak_ptr

I know this is an old question, but I have the same requirement and I'm sure I'm not alone.

The solution in the end for me was to return a function object that returns a boost::optional<> depending on whether the function was called or not.

code here:

#include <boost/optional.hpp>
#include <memory>

namespace value { namespace stdext {

    using boost::optional;
    using boost::none;

    struct called_flag {};

    namespace detail
    {
        template<class Target, class F>
        struct weak_binder
        {
            using target_type = Target;
            using weak_ptr_type = std::weak_ptr<Target>;

            weak_binder(weak_ptr_type weak_ptr, F f)
            : _weak_ptr(std::move(weak_ptr))
            , _f(std::move(f))
            {}

            template<class...Args,
            class Result = std::result_of_t<F(Args...)>,
            std::enable_if_t<not std::is_void<Result>::value>* = nullptr>
            auto operator()(Args&&...args) const -> optional<Result>
            {
                auto locked_ptr = _weak_ptr.lock();
                if (locked_ptr)
                {
                    return _f(std::forward<Args>(args)...);
                }
                else
                {
                    return none;
                }

            }

            template<class...Args,
            class Result = std::result_of_t<F(Args...)>,
            std::enable_if_t<std::is_void<Result>::value>* = nullptr>
            auto operator()(Args&&...args) const -> optional<called_flag>
            {
                auto locked_ptr = _weak_ptr.lock();
                if (locked_ptr)
                {
                    _f(std::forward<Args>(args)...);
                    return called_flag {};
                }
                else
                {
                    return none;
                }

            }

            weak_ptr_type _weak_ptr;
            F _f;
        };
    }

    template<class Ret, class Target, class...FuncArgs, class Pointee, class...Args>
    auto bind_weak(Ret (Target::*mfp)(FuncArgs...), const std::shared_ptr<Pointee>& ptr, Args&&...args)
    {
        using binder_type = decltype(std::bind(mfp, ptr.get(), std::forward<Args>(args)...));
        return detail::weak_binder<Target, binder_type>
        {
            std::weak_ptr<Target>(ptr),
            std::bind(mfp, ptr.get(), std::forward<Args>(args)...)
        };
    }
}}

called (for example) like so:

TEST(bindWeakTest, testBasics)
{

    struct Y
    {
        void bar() {};
    };

    struct X : std::enable_shared_from_this<X>
    {

        int increment(int by) {
            count += by;
            return count;
        }

        void foo() {

        }

        Y y;

        int count = 0;
    };

    auto px = std::make_shared<X>();

    auto wf = value::stdext::bind_weak(&X::increment, px, std::placeholders::_1);
    auto weak_call_bar = value::stdext::bind_weak(&Y::bar, std::shared_ptr<Y>(px, &px->y));

    auto ret1 = wf(4);
    EXPECT_TRUE(bool(ret1));
    EXPECT_EQ(4, ret1.get());

    auto wfoo1 = value::stdext::bind_weak(&X::foo, px);
    auto retfoo1 = wfoo1();
    EXPECT_TRUE(bool(retfoo1));

    auto retbar1 = weak_call_bar();
    EXPECT_TRUE(bool(retbar1));

    px.reset();
    auto ret2 = wf(4);
    EXPECT_FALSE(bool(ret2));

    auto retfoo2 = wfoo1();
    EXPECT_FALSE(bool(retfoo2));

    auto retbar2 = weak_call_bar();
    EXPECT_FALSE(bool(retbar2));


}

source code and tests available here: https://github.com/madmongo1/valuelib

Not sure why that definition is not in boost. There must be a good reason (how to deal with lock fail? Is throwing from there acceptable? Thread safety?) Anyway, that will validate your callee.

namespace boost {

template<class T> T * get_pointer(boost::weak_ptr<T> const& p)
{
  boost::shared_ptr< T > _strong = p.lock();
  if( _strong )
   return _strong.get();
  else
    throw 1;
}

}

int main(int arg, char *argv[])
{
  boost::weak_ptr< MyType > weak_bad;
  {
    boost::shared_ptr< MyType > strong(new MyType);
    boost::weak_ptr< MyType > weak(strong);
    boost::function< void(int) > _func1 = boost::bind(&MyType::setX, weak, _1);
    _func1(10);
    weak_bad = strong;
  }

  try {
    boost::function< void(int) > _func1 = boost::bind(&MyType::setX, weak_bad, _1);
    _func1(10);
  }
  catch(...)
  {
    std::cout << "oops!";
  }

  return 0;
};

Another solution:

You could wrap the std::function. The class producing the callback would hold a shared_ptr< wrapper_type > and provide a weak_ptr< wrapper_type >. The producing object would be the one with the ownership, if it goes out of scope, callers won't be able to promote their weak reference. Your wrapper type could forward call arguments to the std::function or simply expose it via its interface. Just make sure that on copy you properly handle the shared_ptr on the wrapper (don't share).

template< typename _Ty >
struct wrapper
{
  wrapper(_Ty wrappe) 
    : _wrappe(wrappe)
  { }

  _Ty _wrappe;
};

...
boost::shared_ptr< wrapper < std::func< ... > > _func(new wrapper < std::func< ... > );

...
boost::weak_ptr< wrapper < std::func< ... > getCallBack() {
  return _func;
}

How about this? it works only for actions std::function<void()> but perhaps it can be generalized for arbitrarily parameterized functions.

#include <memory>
#include <functional>

template<typename T>
void 
perform_action_or_ignore_when_null(
    std::weak_ptr<T> weak, 
    std::function< void( std::shared_ptr<T> ) > func
    )
{
    if(auto ptr = weak.lock())
        func(ptr);
}

template<typename T>
std::function<void()> 
ignore_when_null(
    std::weak_ptr<T> weak, 
    std::function< void( std::shared_ptr<T> ) > func
    )
{
    return std::bind(perform_action_or_ignore_when_null<T>, weak, func);
}

here's an example usage:

struct Foo {
    Foo() {}
    void bar() { 
        std::cout << "hello world!" << std::endl;
    }
};

void main()
{
  std::weak_ptr<Foo> weakfoo;
  std::function<void(std::shared_ptr<Foo>)> foobar = std::bind(&Foo::bar, std::placeholders::_1);
  {
     auto foo = std::make_shared<Foo>();
     weakfoo  = foo;

     auto f = ignore_when_null(weakfoo, foobar);
     f(); // prints "hello world!";
   }

   auto g = ignore_when_null(weakfoo, foobar);
   g(); // does nothing
}

You can bind weak_ptr to the function as one of parameters,
and check it when the function is called.

For example:

std::function<void()> MyClass::GetCallback()
{
    std::weak_ptr<MyClass> thisWeakPtr(shared_from_this());
    return std::function<void()>(std::bind(&MyClass::CallbackFunc, this,
                                           thisWeakPtr));
}

void MyClass::CallbackFunc(const std::weak_ptr<MyClass>& thisWeakPtr)
{
  if (!thisWeakPtr.lock()) {
    return;
  }

  // Do your callback job.
  // ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM