As mentioned in the previous examples, by default observers are bound to the lifetime of the observed subject. Here’s another demonstration of this behaviour:
#include "react/Domain.h"
#include "react/Event.h"
#include "react/Observer.h"
REACTIVE_DOMAIN(D, sequential)
USING_REACTIVE_DOMAIN(D)
void testFunc()
{
auto trigger = MakeEventSource<D>();
Observe(trigger, [] {
// ...
});
}
After leaving testFunc
, trigger
is destroyed.
This automatically detaches and destroys the observer as well.
We don’t have to worry about resource leaks or that the existence of an observer might prevent the subject from being destroyed.
Alternatively, observers can be detached explicitly before the lifetime of their subject ends. We can either do this externally, or from inside the observer function.
The former is demonstrated here:
void testFunc()
{
auto trigger = MakeEventSource<D>();
ObserverT obs = Observe(trigger, [] (Token) {
cout << "Triggered" << endl;
});
trigger.Emit(); // output: Triggered
obs.Detach(); // Remove the observer
trigger.Emit(); // no output
}
ObserverT
is an alias for Observer<D>
defined by USING_REACTIVE_DOMAIN(D)
.
The observer handle returned by Observe
is stored and by calling its Detach
member function, the underlying observer node is destroyed and the handle becomes invalid.
While it exists and has not been invalidated, an observer handle also takes shared ownership of the observed subject, i.e. by saving it, we state our continued interest in subject. This behaviour comes in handy as shown later.
To create self-detaching observers, the return value of the observer function is changed from void
to ObserverAction
:
void testFunc()
{
auto source = MakeEventSource<D,int>();
Observe(trigger, [] (int v) -> ObserverAction {
if (v != 0)
{
cout << v << endl;
return ObserverAction::next;
}
else
{
cout << "Detached" << endl;
return ObserverAction::stop_and_detach;
}
});
source << 3 << 2 << 1 << 0;
// output: 3 2 1 Detached
source << 4;
// no output
}
Instead of calling Detach
manually, we can utilize the common RAII idiom and transfer ownership of the observer handle to a scope guard:
void testFunc()
{
auto trigger = MakeEventSource<D>();
// Start inner scope
{
ScopedObserverT scopedObs
(
Observe(trigger, [] (Token) {
cout << "Triggered" << endl;
})
);
trigger.Emit();
// output: Triggered
}
// End inner scope
trigger.Emit();
// no output
}
The observer is detached in the destructor of ScopedObserverT
.
One problem of tying the lifetime of the observer to its subject manifests when attempting to do the following:
void testFunc()
{
auto e1 = MakeEventSource<D>();
auto e2 = MakeEventSource<D>();
Observe(Merge(e1, e2), [] (Token) {
cout << "Triggered!" << endl;
});
e1.Emit(); // no output
e2.Emit(); // no output
}
The reason this produces no output is that the result of Merge(e1, e2)
is destroyed after the call, as nobody takes ownership of it.
Consequently, the observer is destroyed as well.
To prevent this, the merged event stream could be kept in variable. Alternatively, we can make use of the fact that the observer handle takes ownership of its subject:
auto obs = Observe(Merge(e1, e2), [] (Token) {
cout << "Triggered" << endl;
});
e1.Emit(); // output: Triggered
e2.Emit(); // output: Triggered