Lambda weak pointer captures

typedef x::ref<widgetObj> widget;

widget w=widget::create();

auto cb=x::functionref<void(int)>::create(
        [w]
        (int n)
        {
               w->method(str, n);
        });

In this example, the callback lambda captures a reference to an object. The lambda holds a reference to the object until the callback itself goes out of scope, and gets destroyed.

Sometimes it's desirable for lambdas to capture a weak pointer reference to an object, so that the existence of the callback does not affect the scope of the object's existence, if possible. When the lambda gets invoked, an attempt gets made to recover a regular strong reference to the object, and then use it, and take no action if the object already went out of scope and got destroyed.

This is often the case when the callback is closely related to the object. Very often, the object itself will want to install a callback lambda that uses the object's own method when it gets invoked, with the object owning the only strong reference to an object. For example:

class bObj;

typedef x::ref<bObj> b;

class aObj;

typedef x::ref<aObj> a;
typedef x::ptr<aObj> aptr;

class aObj : virtual public x::obj {

    x::functionptr<void()> b_callback;

public:

    void initialize(const b &b_object)
    {
        auto cb=x::functionref<void()>::create(
	    [weak_a=x::weakptr<aptr>(aptr(this))]
	    {
	        auto a_strong=weak_a.getptr();

		if (!a_strong.null())
		    a_strong->do_something();
	    });

        b_object->install(cb);
        b_callback=cb;
    }

    void do_something();
};

This is an example of an instance of one class, aObj, installing a callback in an instance of a second class, bObj. When the second class invokes the callback, the callback then invokes the do_something() method of the first class.

With this approach, the second class itself maintains only weak references to the installed callbacks (typically using a weak list of callbacks. When all other references to the first class go out of scope and get destroyed, the only strong reference to the callback also goes out of scope, and the callback itself gets destroyed, removing it from the second class's weak list container.

In this example, the lambda must capture a weak pointer to its own class instance, otherwise a circular strong reference gets created: the class instance owns a strong reference to the callback, and the callback owns a strong reference on the same class instance. In this case, other measures must get implemented to explicitly deinstall and destroy the callback, in order for all references here to go out of scope, and the underlying objects gets destroyed.

The callback recovers a strong to its class instance, and invokes its method. Note that it's possible that while the callback is holding its own temporary strong reference to the class instance, all other strong references to the class instance go out of scope. When the do_something() method returns, the temporary reference to the instance will go out of scope, and with that being the last reference to the instance, it finally gets destroyed.

#include <x/weakcapture.H>

auto a=x::ref<aObj>::create();

auto b=x::ref<bObj>::create();
// ...

auto cb=[refs=x::make_weak_capture(a, b)]
	{
           auto got=refs.get();

           if (got)
           {
              auto& [a, b]=*got;

              // ...
           }
        };
        

x::make_weak_capture() takes a list of x::refs to objects, and returns an opaque container of weak references to all the referenced objects. This ends up being captured by the lambda.

The opaque container's get() method recovers the strong references from the weakly-referenced objects, and returns a std::optional of a std::tuple of the original strong references, provided that none of the objects went out of scope and got destroyed. The std::optional will not contain the tuple if one or more of the original object is destroyed. It's an all-or-nothing proposition.

With this approach, the callback action easily chooses to take action only if all weakly-referenced objects still exist. A common situation is that the callback needs to be in effect if several objects still exist, but without capturing its own references to the objects. This design pattern takes care of a race condition where the related related objects are in the middle of getting destroyed, but some of them still exist.