signal
manages a list of connections to slots and invokes the slots during its emissions. It performs the necessary synchronization to ensure safety in multithreaded environments.
signal();
Constructs a new signal
instance with no connected slots.
Throws an exception if memory allocation fails.
~signal() noexcept;
Destroys the signal
instance and disconnects all connected slots, if any.
If destructor is invoked during signal emission (even if a called slot destroys the signal), emission stops immediately, all slots are disconnected, and the last thread using the signal performs cleanup.
-
template<typename Ty, typename Fn>
unsigned connect(Ty * object, Fn slot, exec type = {});
-
template<typename Fn>
unsigned connect(Fn slot);
Connects the signal
to the given slot
.
- Connects object and method, or
context
and callable. - Connects callable
slot
is any callable that can be invoked with argument types specified in the signal
's signature. A signal-slot type mismatch results in a compilation error.
If object
or slot
or both are null pointers, connection is not established and id of value 0
is returned (see below).
If slot
is a pointer to method, object
must be a pointer to object the method should be called on. If slot
is any other callable and object
is still provided, it must specify the associated context
object (see context
class).
If object's class has unambiguous and accessible context
base class, the object obtains the following additional features:
slot
will be invoked in the threadcontext
object lives in (default behavior);- once the
context
object gets destroyed, its associated slots will no longer be reachable for signal emissions.
If context
object is provided, type
may specify the connection type: synchronous or asynchronous. The default is neither, so the signal detects how to call the target slot automatically, based on the context
's thread affinity. However, type
may be explicitly set as exec::sync
or exec::async
.
If connection has been established successfully, returns the connection's id value that can be used to disconnect the connection later; otherwise, returns 0
. Note! Disconnection by id is used mainly for lambdas and other targets that do not have accessible equality comparison operator. For equality comparable targets it is more convenient to use disconnection by value (see disconnect()
method).
Throws an exception if memory allocation fails.
-
template<typename Ty, typename Fn>
bool disconnect(Ty * object, Fn slot) noexcept;
-
template<typename Fn>
bool disconnect(Fn slot) noexcept;
-
template<typename Fn>
bool disconnect(unsigned id) noexcept;
-
template<typename Fn>
void disconnect() noexcept;
Disconnects the signal
from the given slot
.
- Disconnects object and method.
- Disconnects callable.
- Disconnects connection identified by
id
. - Disconnects all slots.
slot
is any callable that can be invoked with argument types specified in the signal
's signature. A signal-slot type mismatch results in a compilation error.
If object
or slot
or both are null pointers, disconnection is not performed and false
is returned.
If slot
is a pointer to method, object
must be a pointer to object the method should be called on.
If slot
is not equality comparable, disconnection is not performed and false
is returned. Two slots are equal, if they are of exactly the same static type with accessible equality comparison operator, and the result of their comparison is true
.
Note! If slot
is not a pointer to method and is associated with some context
object, that context
object must be omitted. The reason is that for non pointer-to-member slots, the context
object is not a part of the slot, but rather just a helper object that does not participate in the slot invocation itself.
If disconnection is successful returns true
; otherwise, returns false
.
void emit(ArgTs ... args) const;
void operator()(ArgTs ... args) const;
Emits the signal
. The two methods are identical.
During signal emission all connected slots are invoked with provided arguments. The exact invocation order is unspecified.
Each slot in the signal's list is invoked synchronously or asynchronously depending on an associated context
object, if any, and depending on execution type explicitly specified in connect
method. If no execution type was specified, the default rule is used:
if the
signal
is emitted from the same thread thecontext
object lives in, thecontext
's slots are invoked synchronously; otherwise the call is transferred intocontext
's thread and invoked asynchronously.
If no context
object is associated with a slot, the slot is always invoked synchronously.
Note! During asynchronous slot invocations arguments and some necessary data must be copied into dynamic memory.
These methods are lock-free, except when an unreachable "expired" context
has been detected. In this case the internal lock has to be acquired in order to disconnect the unreachable slot.
If the signal
is blocked, the methods do nothing.
Throws an exception if memory allocation fails.
bool block(bool yes = true) noexcept;
Blocks or unblocks the signal
.
Returns true
, if the signal was blocked prior to the call, false
otherwise.
bool blocked() const noexcept;
Checks whether the signal
is blocked.
Returns true
, if the signal is blocked, false
otherwise.
Note! The result of this method call is only an approximation. If the signal is blocked and unblocked by multiple threads, the returned value may not reflect the current blocking state.
signal(const signal &) = delete;
signal(signal &&) = delete;
signal & operator=(const signal &) = delete;
signal & operator=(signal &&) = delete;
signal
is neither copy | move constructible nor copy | move assignable.
Base class context
provides context for slot invocations. It associates thread affinity with an instance of a derived class and performs its automatic lifetime tracking.
A context
object always belongs to the thread that created it. Therefore, all signal emissions targeting this object will be executed in the thread it belongs to (default behavior). This design guarantees absence of any race conditions when the context
's slots are invoked as a result of signal emissions from multiple threads;
all slot invocations will be serialized.
Note! All actions taken on a context
object (such as destruction or direct method calls) should be performed in the thread the context
belongs to.
A class inherited from context
can provide thread affinity and automatic lifetime tracking not only to its own methods, but also to any callable that is associated with the class instance in signal::connect()
method.
context();
Constructs a context
object and associates it with the calling thread.
Throws an exception if memory allocation fails.
~context() noexcept;
Destroys the context
and releases any acquired resources.
context(const context &) = delete;
context(context &&) = delete;
context & operator=(const context &) = delete;
context & operator=(context &&) = delete;
context
is neither copy | move constructible nor copy | move assignable.
exec::sync
exec::async
Enumeration that specifies slot execution type.
- Slot is always executed synchronously.
- Slot is always executed asynchronously.
This enumeration should be used with extreme care! Synchronous execution of slots belonging to different threads may lead to race conditions.
-
bool signals_execute();
-
bool signals_execute(unsigned number);
Executes signal emissions received in the current thread.
- Executes at most one slot call.
- Executes at most
number
of slot calls.
This method never blocks; if there are no pending slot calls for the current thread it just returns.
In general, any thread that wishes to receive cross-thread signal emissions just calls this method in a loop. Such design allows easily integrate cross-thread signals into any event loop (for example, window messaging) that exists in a target application or framework.
Returns true
if at least one slot call has been executed, false
otherwise.
The lite
(single-threaded) version of the library is located in vdk::lite
namespace. It provides APIs nearly identical to the multithreaded version. The differences specific to the lite
version are:
- There is no
exec
enumeration. - There is no thread affinity associated with a
context
instance. - All slots are always invoked synchronously.
Note! lite
version should not be used in a multithreaded environment. Accessing the same library's objects from multiple threads, directly or indirectly, results in catastrophic race conditions and completely unpredictable behavior.
The library allocates dynamic memory through the centralized global memory resource. By default, this memory resource uses plain operator new
and operator delete
. It is possible to install a custom user-provided memory resource instead.
A custom memory resource can be installed through the experimental API - vdk::memory::signals::memory()
function that accepts and stores pointer to an instance of a class inherited from vdk::memory::signals::memory_resource
.
Note! The memory resource can be installed only once. This avoids many problems that could otherwise arise from incompatible memory resources trying to deallocate each other's memory.