ImFusion SDK 4.3
Signals and Slots

Lightweight implementation of the signal-slot design pattern. More...

+ Collaboration diagram for Signals and Slots:

Detailed Description

Lightweight implementation of the signal-slot design pattern.

Overview

Signals and slots are a variation of the Observer design pattern and used for communication between objects. This enables two classes to interact with each other without establishing a tight coupling. An observed object (subject) can notify its observers of state changes by calling a function defined by the observer.

The ImFusion SDK makes use of different interfaces for implementing the Observer pattern/event-based programming:

The ImFusion signal-slot API

Use SignalImpl to define a signal. You can add any amount signals as member variables to a class. Since specifying the presence of a mutex (first template parameter) is cumbersome, it is recommended to use one of the two aliases provided: Signal for signals without mutex, ProtectedSignal for signals with a mutex. The variadic template parameter of the signal defines the number and types of the parameters passed when notifying the observers. Within the ImFusion SDK we use the convention that signals members of functions are declared public and named using signal as prefix.

The following example class has one unprotected (no mutex) signal without payload and one protected (with mutex) signal that will cary one int and one std::string as payload when notifying its observers.

class MySubject
{
public:
Signal<> signal1;
ProtectedSignal<int, std::string> signal2;
};
MySubject subject;

You can connect any function of matching signature to a signal. One way is to call SignalImpl::connect using a plain lambda function:

subject.signal2.connect([](int i, std::string s) {
std::cout << "Signal2 was emitted with int " << i << " and string " << s << ".";
});

However, using plain lambdas has one significant downside/limitation: It does not provide any automatic lifetime tracking of established connections. If the observer or any object referenced in the lambda function is deleted, the signal will still have the connection and try to notify the deleted observer.

One possibility to avoid crashes in these situations is to manually keep track of the observer lifetime and remove the connection when needed using the SignalConnection object:

// create a connection and store the connection object.
std::shared_ptr<const SignalConnection> connection = subject.signal1.connect([]() { std::cout << "Signal1 was emitted."; });
// ...
// when needed disconnect the observer
connection->disconnect();

Since such manual lifetime management is cumbersome and error-prone, the signal-slot interface provides an automatization for this. Let the observer inherit from SignalReceiver and pass a pointer to the object when connecting to the signal. The signal-slot interface will establish an automatic bi-directional lifetime tracking: When either of the two ends of the connection is deleted the connection will be automatically removed as well.

// Inherit from SignalReceiver to allow for lifetime-tracking.
class MyObserver : public SignalReceiver{public: void onEvent(){std::cout << "MyObserver has been notified.";
}
}
{
MyObserver observer;
// establish automatic lifetime tracking by passing a reference to the observer instance
subject.signal1.connect(&observer, [&observer]() { observer.onEvent(); });
// alternative/simpler overload for member functions:
subject.signal1.connect(&observer, &MyObserver::onEvent);
}
// Since MyObserver inherits from SignalReceiver and the object instance was provided when
// connecting to the signal, both connections will be disconnected automatically when observer
// is destroyed on scope exit.

For more details, please have a look at the documentation of SignalImpl and SignalReceiver.

Temporarily Blocking Notification of Observers

Sometimes it is required to temporarily disable the emitting of a signal to avoid recursive signalling loops. The preferred solution is to use the SignalBlocker class providing a very convenient and exception-safe interface:

MyObserver observer1, observer2;
auto connection1 = signal.connect(&observer1, &MyObserver::onEvent);
auto connection2 = signal.connect(&observer2, &MyObserver::onEvent);
{
SignalBlocker blocker(signal); // temporary blocks all connections of signal
signal.emitSignal(); // neither observer1 nor observer2 will be notified
}
{
SignalBlocker blocker(*connection1); // temporary blocks only connection1
signal.emitSignal(); // only observer2 will be notified
}
{
SignalBlocker b(signal, observer2); // temporary blocks all connections between signal and observer2
signal.emitSignal(); // only observer1 will be notified
}
// notifies both observer1 and observer2 since signals/connections are no longer blocked
signal.emitSignal();

Alternatively, you can use the low-level functions SignalImpl::setBlocked() and SignalConnection::setBlocked() to block notifications from an entire signal or for a single connection, respectively.

Classes

class  DeprecatedSignal< MainSignalType, ArgTypes >
 Deprecation layer for signals. More...
 
class  SignalReceiver
 Base class for classes that can contain slots (i.e. More...
 
class  SignalConnection
 Structure for describing/identifying individual connections between signals and slots. More...
 
class  SignalImpl< UseMutex, ArgTypes >
 Implementation for a specific signal with the given signature. More...
 
class  SignalBlocker
 Convenient scope guard class to temporarily block signals/connections so that observers are not notified. More...
 
class  SignalBase
 Base interface for Signals. More...
 

Typedefs

template<typename... ArgTypes>
using Signal = SignalImpl<false, ArgTypes...>
 Alias for a non-protected signal, which is reentrant but not thread-safe.
 
template<typename... ArgTypes>
using ProtectedSignal = SignalImpl<true, ArgTypes...>
 Alias for a protected signal, which is thread-safe.
 
Search Tab / S to search, Esc to close