It is possible to implement a callback design pattern by
combining function reference
objects together with
weak containers.
This is done with a weak container of
x::functionObj<
objects, the objects referenced by
signature
>x::functionref
s
and
x::functionptr
.
The weak container represents a collection of function objects that get invoked to report an event of some kind. Each callback gets installed as a function object, serving as its own mcguffin:
A x::weaklist
typically represents a list of installed callbacks, in an unspecified
order.
It's also possible to use a
x::weakmap
or a
x::weakmultimap
to represent a prioritized list of callbacks, ordered by the map key.
#include <x/functionalrefptr.H> #include <x/weaklist.H> typedef x::functionref<void (int)> callback_t; typedef x::functionptr<void (int)> callbackptr_t; typedef x::weaklist<x::functionObj<void (int)>> callback_list_t; callback_list_t callback_list=callback_list_t::create(); // .... callback_t new_callback=[] (int callback_argument) { // Callback action }; // Install the callback: callback_list->push_back(new_callback);
Here, the weak container represents a list of installed callbacks.
new_callback
is a new function reference object
that's been installed in this callback container. When the last reference
to the referenced function object goes out of scope and it gets
destroyed, it gets automatically removed from the weak container.
Invoking all the current callbacks is accomplished simply by iterating over the weak container.
for (const auto &ptr : *callback_list) { callbackptr_t p=ptr.getptr(); if (p) { p(4); } }
With multiple execution threads, it is possible to have a different
execution thread invoke a particular callback just after the
last reference to the callback object appears to go out of scope
and get destroyed.
Since getptr
()
recovers a regular, strong reference
from the weak pointer the execution thread that's invoking
the callbacks could end up holding the last remaining reference to
the callback object, and be executing it at the same time that
the last existing reference to the object goes out of scope.
It is important to understand that with multiple execution threads, it's possible for a callback to still get invoked after its object gets supposedly destroyed, for one last time.
These two template functions generate the typically code for iterating over the callback container, and invoking each callback:
#include <x/invoke_callbacks.H> x::invoke_callbacks(callback_list, 4); x::invoke_callbacks_log_exceptions(callback_list, 0);
x::invoke_callbacks
()'s first parameter is the
callback list.
x::invoke_callbacks
() invokes each callback in the
weak list,
with its remaining arguments forwarded to each callback.
x::invoke_callbacks
() does not catch
any x::exception
s
thrown from an invoked callback. A thrown exception stops
the iteration, and
any remaining callbacks in the list from getting invoked.
x::invoke_callbacks_log_exceptions
()
catches any x::exception
that gets thrown
from an invoked callback. The caught exception is logged before
the next callback in the weak list gets invoked.
Callbacks that return non-void
values require two extra parameters:
typedef x::weaklist<functionObj<int(int)>> callback_list_t; callbacks_list_t callback_list=callback_list_t::create();
This is a container of callbacks that not only take an
int
parameter, but the callbacks also
return an int
parameter.
x::invoke_callbacks
(), and
x::invoke_callbacks_log_exceptions
()
now take two extra lambda parameters, after the weak container
passed as the first parameter (and any remaining parameters get
forwarded to each callback, as usual):
int retval=x::invoke_callbacks(callbacks, [] (int value) { return value < 0; }, [] { return 0; }, "Hello world", 0);
Using x::invoke_callbacks
() and
invoke_callbacks_log_exceptions
() with a weak list
of callbacks that return a non-void
value requires
the following parameters:
The weak container of callbacks, as usual.
A lambda that checks the return value from each callback.
If the lambda returns true
, callback invocation
stops.
Any remaining callbacks do not get invoked, and
x::invoke_callbacks
() or
x::invoke_callbacks_log_exceptions
() returns
immediately, with the last callback's return value.
A lambda that returns the value that
x::invoke_callbacks
() or
x::invoke_callbacks_log_exceptions
()
itself returns
if the weak list is empty, or if the first lambda returned
false
for every invoked callback's value.
The remaining parameters get forwarded to each invoked callback.