It is possible to make arrangements to have a closure, or some
callable object, a.k.a. a “callback” get invoked
when a specific x::obj
or its subclass,
a.k.a. a “mcguffin”, gets destroyed when the last x::ref
or an x::ptr
to the mcguffin goes out of scope.
This object then becomes a “mcguffin”. This name comes from
the film director Alfred Hitchcock who used it
to describe some minor prop or gadget that is of little inherent value,
but is responsible for driving the major plot of a movie.
(Although “macguffin” is the more popular spelling, I like
the “mcguffin” spelling better, and I'm the one
writing this documentation).
A mcguffin is a reference-counted object whose methods and members may not be necessarily important, but whose own implicit existence has some inherent purpose, and defines some part of its functionality, loosely speaking. Consider a typical situation of a thread sending some kind of a message to another thread. The first thread wants to know when the second thread processed this message.
You can write and code a separate notification mechanism of some kind, but this quickly gets complicated. What if, for example, the second thread ended normally, but before it had a chance to process the message? You'll have to handle that eventuality too. With typical situations there will be other ways for things to go off the beaten path, too.
A mcguffin-oriented approach is simpler. The message to a thread is some kind of an object, which serves double-duty as a mcguffin. When the message gets received by the thread, processed, and discarded, the last reference to the message object goes out of scope and the mcguffin gets destroyed. If the thread stops, presumably its message queue, together with all of its messages, also gets destroyed. In all cases, the message object gets destroyed. The message object is a mcguffin because its destructor callbacks get invoked in all cases.
A mcguffin may be a very distinguished class on its own merits. It might have many methods and members of its own; but it's a mcguffin because it exists, and for no other reason.
A mcguffin can have more than one destructor callback.
All destructor callbacks get invoked, one at a time, when
the last x::ref
or an x::ptr
to the mcguffin, a
reference-counted object goes out of scope
and
the object gets destroyed. The destructor callbacks get invoked
immediately after the object's destructor returns and the object
gets destroyed.
Once invoked, each destructor callback does whatever it wants to do.
The callbacks get invoked by whichever thread destroyed the last
reference to the object, not necessarily the
same thread that created it!
By the time the destructor callbacks get called, the mcguffin is already destroyed, and the destructor callback has no means to access it. But that's fine, the object's existence was what made it a mcguffin, and now the destructor callbacks get invoked because the object was destroyed.
#include <x/ref.H> #include <x/obj.H> #include <x/ondestroy.H> class mcguffinObj : virtual public x::obj { // ... }; auto mcguffin=x::ref<mcguffinObj>::create(); // ... myobj->ondestroy([] { // ... });
The above example attaches a destructor callback to
an instance of a mcguffinObj
. This
class may have its own
methods, or serve some particular purpose that's unrelated to its
role as a mcguffin. The above example leaves its methods unspecified.
They don't matter. It's a mcguffin.
For a
“pure” mcguffin, the destructor callback
gets attached to a plain
x::ref<x::obj>
.
An x::obj
is nothing more than the base class
for reference-counted objects, and a mere reference to this object
has no other functionality, except serving as a convenient mcguffin
for any occasion.
See the x::ptr
documentation for more information on destructor callbacks.
Destructor callbacks should not throw exceptions, because it essentially becomes a part of the mcguffin's destructor, and cannot throw any exceptions. All exceptions must be caught.
x::destroy_callback
is a simple elementation of a common design pattern where some
mcguffin's destruction does not itself takes any immediate action.
Rather, at some point later, a check is made whether the object is
already destroyed. If not, wait for its destruction.
#include <x/destroy_callback.H> x::destroy_callback cb=x::destroy_callback::create(); // ... x::ref<x::obj> mcguffin; mcguffin->ondestroy([cb] { cb->destroyed(); }); // ... cb->wait();
x::destroy_callback
implements
wait
() which does not return until
its destroyed
() gets invoked, presumably
by another thread.
wait
() returns immediately
if destroyed
() was already
invoked.
x::destroy_callback
is designed
for simple one-off situations involving a single mcguffin.
It's your responsibility to attach it as a destructor callback to
at least one object, before invoking
wait
(). If it's not been
ondestroy
()ed anywhere, its
destroyed
() never gets invoked, and
wait
() waits forever.
A
x::destroy_callback::base::guard
is an object meant to be instantiated in automatic scope or as a class
member.
Its destructor waits until the objects placed in its care get
destroyed, before the destructor itself returns:
void run() { x::destroy_callback::base::guard guard; x::ref<x::obj> mcguffin; guard(mcguffin); // ... }
When run
()'s scope exits, its auto-scoped objects
get destroyed in reverse order of declaration. But even after this
mcguffin
gets destroyed, other references to the
mcguffin object might exist somewhere else, and/or used by other
threads.
Use this guard object when you want to wait for these mcguffins to really go out of scope and get destroyed. The guard object's destructor waits indefinitely until the guarded objects gets destroyed, if they're not destroyed already.
More than one object can be guarded. The guard's
operator()
attaches a destructor callback to the
mcguffin, and the guard's own destructor waits until all attached
destructor callbacks get invoked, if they're not already invoked.
The guard's
operator()
is a template function that returns
the same x::ref
that was passed to it. That makes it possible to do
something like this:
void run() { x::destroy_callback::base::guard guard; start_timer(guard(x::ref<x::obj>::create())); // ... }
start_timer
() is some external function that takes
a mcguffin as an argument. The mcguffin is no longer needed after
invoking it, and run
() has no further use for it;
and no more references to the mcguffin should remain,
start_timer
() owns it entirely.
Here, guard's operator()
does its job, returns
whatever x::ref
it got, which gets subsequently passed to
start_timer
().
Subsequently, when guard
goes out of scope, it
waits for the mcguffin to get destroyed, wherever it is.
x::destroy_callback::base::guard
does not implement copy contructors or assignment operators.
It should always be instantiated either in automatic scope, or as a
class member.
x::destroy_callback::base::guard_object
is a convenient template to instantiate a guard for a single
x::ref
.
typedef x::ref<widgetObj> widget; class containerObj : virtual public x::obj { x::destroy_callback::base::guard_object<widget> title; // ... }; // ... containerObj::containerObj() : title(widget::create()) { // ... title->method(); }
x::destroy_callback::base::guard_object
's
template parameter must be an x::ref
.
x::destroy_callback::base::guard_object
declares a subclass of this x::ref
, that's privately guarded.
When x::destroy_callback::base::guard_object
goes out of scope and gets destroyed, if there are any other x::ref
s
or x::ptr
s
to the same object,
x::destroy_callback::base::guard_object
waits until all other x::ref
s and x::ptr
s go out of scope and gets
destroyed.
x::destroy_callback::base::guard_object
's
destructor does not return until the referenced object's destructor
returns.
x::destroy_callback::base::guard_object
inherits
*
() and
->
() operators from the underlying
x::ref
, so that it can be used just like one. Although the
guard object itself is not copy-constructible or assignable, the
x::ref
can be copied and passed around (with all copies required to be
destroyed, before the guard's destructor returns).
Attaching an ordinary destructor callback to a mcguffin is an irrevocable act. The destructor callback gets attached, and it gets invoked whenever the mcguffin ends up getting destroyed.
Instead of invoking some mcguffin's ondestroy
()
method to attach a destructor callback to the mcguffin,
construct an
x::ondestroy
class instance:
#include <x/ondestroy.H> class mcguffinObj : virtual public x::obj { // ... }; x::ref<mcguffinObj> mcguffin=x::ref<mcguffinObj>::create(); auto ondestroy_hook=x::ondestroy::create([] { // ... }, mcguffin, true); // ... ondestroy_hook->cancel();
The destructor callback lambda stays attached
to its mcguffin until x::ondestroy
's
cancel
() gets invoked. If the mcguffin is
not destroyed by the time
cancel
() gets invoked,
the destructor callback does not get invoked as part of the mcguffin's
eventual destruction. If the mcguffin is
already destroyed, and its destructor callbacks were already invoked,
cancel
() has no effect.
x::ondestroy
itself is a reference to
a reference-counted object, that can be freely passed around or copied.
If the last parameter to
x::ondestroy::create
is true
,
cancel
() gets automatically invoked when
the x::ondestroy
-referenced instance itself
goes out of scope and gets destroyed. With false
,
when x::ondestroy
gets destroyed the
destructor callback becomes permanent.
If another thread is already running a just-destroyed object's
callbacks, invoking cancel
() explicitly
blocks until the object's callbacks return.
An explicit cancel
()'s contract is that the
destructor callback's
destroyed
() can never get called after
cancel
() returns.
A cancel
() that's automatically invoked, when
x::ondestroy
goes out of scope and gets
destroyed, does not block if another thread is executing the object's
destructors.
This is to allow an x::ondestroy
to go out
of scope and get destroyed as a
direct or an indirect result of the same object's destructor
callback getting invoked. Otherwise, this ends up deadlocking the
thread with itself: something is executing the object's destructor
callbacks, and x::ondestroy
's destructor
needs to wait for the callback to return, but that can't happen until
the destructor itself returns.
#include <x/ondestroy.H> // ... std::list<x::ref<x::obj> > objects; x::on_any_destroyed([] { // Something was destroyed }, objects.begin(), objects.end());
x::on_any_destroyed
() invokes a lambda
when any one of several objects gets destroyed. Its parameters are
a lambda to invoke, and a
a beginning and an ending input iterator for a sequence of references
to reference-counted object class instances.
x::on_any_destroyed
() results in the lambda
getting invoked when
any of the objects in the sequence go out of scope and gets destroyed.
The lambda callback gets invoked only once; for all
practical matters, for the first object in the sequence that goes out
of scope and gets destroyed. Subsequent destruction of other objects
in the sequence does not result in
the lambda getting invoked on their behalf.
The input sequence is expected to produce strong x::ref
s only. However,
it's an arbitrary input sequence. Once an x::ref
has been iterated over,
it is no longer in scope, and, with a custom input iterator, its
possible that it can get destroyed. If so, it's possible that
the lambda gets called even before
x::on_any_destroyed
() returns.
The lambda also gets called, prior to
x::on_any_destroyed
() returning in the event
that the iterators define an empty input sequence.
x::on_any_destroyed
() works by attaching
an x::ondestroy
()
callback to all of the objects in the sequence to a mcguffin that
invokes the lambda, with the first
x::ondestroy
()
call releasing the mcguffin, whose destructor invokes the lambda.
If the iterator sequence is empty, the mcguffin goes out of scope
immediately, prior to
x::on_any_destroyed
() returning.