c++-gtk-utils
|
This file provides a thread-safe signal/slot mechanism, with automatic disconnection. More...
#include <unistd.h>
#include <list>
#include <unordered_set>
#include <algorithm>
#include <functional>
#include <utility>
#include <type_traits>
#include <c++-gtk-utils/callback.h>
#include <c++-gtk-utils/mutex.h>
#include <c++-gtk-utils/cgu_config.h>
Go to the source code of this file.
Classes | |
class | Cgu::Releaser |
A class used for tracking EmitterArg and SafeEmitterArg connections. More... | |
class | Cgu::EmitterArg< FreeArgs > |
A class to execute callbacks connected to it, with provision for automatic disconnection. More... | |
class | Cgu::SafeEmitterArg< FreeArgs > |
A thread-safe class to execute callbacks connected to it, with provision for automatic disconnection. More... | |
Namespaces | |
Cgu | |
Typedefs | |
typedef EmitterArg | Cgu::Emitter |
typedef SafeEmitterArg | Cgu::SafeEmitter |
This file provides a thread-safe signal/slot mechanism, with automatic disconnection.
An EmitterArg object is a list of Callback::FunctorArg objects. Either callable objects (such as lambda expressions or the return value of std::bind) or Callback::FunctorArg objects may be "connected" to the EmitterArg object, and these will be executed when the operator()() or emit() member functions of the EmitterArg object concerned is called. They will be called in the order in which they were connected. Emitter is a typedef for EmitterArg<>. The generalised EmitterArg<T...> type contains Callback::FunctorArg<T...> objects or callable objects converted to Callback::FunctorArg<T...> objects (types T... being the unbound arguments the callback - see Cgu::Callback for further details, and "Usage" below for examples.) The Emitter type holds Callback::Functor (namely Callback::FunctorArg<>) objects.
The main advantage of an emitter object as opposed to storing a functor object directly, apart from the fact that more than one functor can be dispatched by a single call to EmitterArg::emit() or EmitterArg::operator()(), is that it provides for automatic disconnection of a functor if the object whose member function it represents or calls into has ceased to exist.
Where automatic disconnection is wanted, the object whose method is to be encapsulated by a functor must have a Releaser object as a public member. The Releaser object should be passed as the second argument of EmitterArg::connect(). As well as a Releaser object informing an emitter object when it has ceased to exist, an emitter object will do the same to the Releaser object if the emitter object happens to be destroyed before an object whose members it references or calls into (and therefore before the Releaser object). Automatic disconnection is mainly useful for non-static member functions, but it can be employed for static member functions or non-member functions if wanted (that will in effect bind the lifetime of the functor to that of the object to whose Releaser the functor has been attached.)
It is safe for a connected function (i) to delete the EmitterArg object to which it is connected, even if there are other functors still to execute in the same emission (which will execute normally provided they do not try to call any of the emitter's functions), (ii) to call 'delete this' nothwithstanding that the connected functor is protected by a Releaser object (assuming all the other restraints on calling 'delete this' are met), provided that no other access would be made to the deleted object in a function call connected to the same emitter which is due to execute subsequently in the same emission, and (iii) to disconnect itself from the EmitterArg object. This design approach has a trade-off: if a connected functor tries to block, unblock or disconnect another functor connected to the same EmitterArg object which is due to execute subsequently in the same emission (or to block, unblock or disconnect itself when it is due to execute again subsequently in the same emission), the attempted block, unblock or disconnection will not have any effect on that emission (it will only have effect on a subsequent emission). In addition, a connected functor may not destroy an object which has a non-static method called by another functor connected to the same emitter and which would execute subsequently in the same emission, even if that object is protected by a Releaser object (the non-static method will unsuccessfully attempt to execute notwithstanding the destruction of the object it would be operating on).
The SafeEmitterArg classes are the same as their EmitterArg counterparts except that they contain Callback::SafeFunctorArg objects, and their emit(), operator()(), connect(), disconnect(), block(), unblock() and destructor methods are protected by a mutex so that different threads can call these methods on the same emitter object, or create and delete the object.
Note that the mutexes are released when the operator()()/emit() methods of the relevent Callback::SafeFunctorArg objects are called, as SafeEmitterArg objects have no idea what the referenced callbacks will do so if they were not released deadlocks could arise from recursive or out-of-order locking of the SafeEmitterArg mutex. It is therefore for users to provide additional synchronisation if the functions encapsulated by the relevant functors themselves need additional protection. Note also the subsidiary thread-safety points mentioned below.
The Releaser class is intrinsically thread safe (the overhead of locking is so low that it is pointless having a separate unprotected class). This means that if a program is multi-threaded, you can use the plain EmitterArg classes provided that only the thread which creates a particular EmitterArg object calls connect(), block(), unblock((), emit() or operator()() on it, or deletes it, or calls disconnect() on it (either directly or through a Releaser object being destroyed). Where more than one thread might do that in relation to any one emitter object, use SafeEmitterArg.
These classes are intended as a lightweight thread-safe signal/slot mechanism for GUI programming. For more demanding usage libsigc++ is a good choice, except that it is not thread-safe. An alternative to libsigc++ is the boost::signal2 module, which is thread-safe.
As mentioned, the SafeEmitterArg classes are thread safe, and their methods can be called in different threads without ill effect. However, there are some things that cannot be done. Users should observe two points.
First, it has been mentioned that if a connected functor blocks, unblocks or disconnects another functor connected to the same emitter object and due to execute subsequently in the same emission, the blocking, unblocking or disconnection will not have effect in that emission, and that a connected functor may not delete an object whose non-static method is due to execute subsequently in the same emission. The same outcome would result if another thread tries to do any of these things while an emission is under way. Where a non-static method of an object is called by a connected functor, another thread should not try to delete the object from the time of SafeEmitterArg's operator()() or emit() beginning to the time of it ending, even if the object is protected by a Releaser object.
Secondly, when a Releaser object is passed as the second argument to the connect() method of a SafeEmitterArg object, the Releaser object must remain in existence until the connect() method returns or the emitter may be left in an inconsistent state.
EmitterArg and SafeEmitterArg objects cannot be copied. Releaser objects can be (we do not want to make a class uncopiable just because it has the safety feature of having a Releaser object as a member).
So how should assignment of a Releaser object and of a class which has a Releaser as a member be handled? An object which has a Releaser as a member and which is being assigned to (the assignee) could keep all its existing pre-assignment emitter connections - so far as the Releaser object is concerned, it will have to do so where the connections are not protected by the Releaser object, and we could do the same in relation to protected connections, in which case we would make operator=() of Releaser do nothing: that is, just return - a default assignment would always be wrong as it would take the assignor's Releaser state but inherit none of its connections, which the assignee cannot inherit as they depend on a remote emitter object or objects.
However, the state of the assignee after assignment may not be such as to permit the inheriting of all the assignor's state except its connections. Accordingly, the default strategy adopted here is for the Releaser object to become a blank sheet on assignment. After assignment, an assignee which has a Releaser object as a member will no longer have any of the emitter connections which were, prior to assignment, protected by the Releaser object. If in a particular case the user does not want this behaviour, she should provide an assignment operator for the class which has Releaser as a member and leave Releaser alone in the assignment operator.
These are examples:
Callback::FunctorArg and Callback::SafeFunctorArg objects may be connected to an emitter, and the connect() method may be directly initialized with the result of Callback::make(), Callback::make_ref() or Callback::lambda() and implicit conversion will take place. Here is an example using Callback::make_ref(), with a class object my_obj of type MyClass, with a method void MyClass::my_method(const Something&, int):
EmitterArg classes do not provide for a return value. If a result is wanted, users should pass an unbound argument by reference or pointer (or pointer to pointer).
Apart from the emit()/operator()() and connect() methods, nothing done to an EmitterArg/SafeEmitterArg object should cause an exception to be thrown. This is because other methods only iterate through a std::list object using std::for_each(), std::find() or by hand, and the only things done by std::for_each() or after a std::find() or iteration is to remove a functor from the list (copying a functor and comparing functors never throw, nor does destroying a functor provided the destructors of any bound argument type do not throw). Thus, an EmitterArg/SafeEmitterArg and Releaser object should never get into an inconsistent state.
The connect() method could throw a std::bad_alloc exception, either on creating new functors or on pushing the functors onto the list. However, were it to do so, the method has strong exception safety (assuming merely iterating over a list does not throw, as it should not).
The emit()/operator()() methods could throw std::bad_alloc, and so far as that is concerned emission of all the connected functors will either all succeed or all fail. In addition, the functions represented by the functors held by the emitter might throw when executed. emit()/operator()() do not attempt to catch these exceptions as there is nothing they could do with them. This means that although a throwing connected functor will not leave the EmitterArg/SafeEmitterArg object in an inconsistent state, any other connected functors due to execute subsequently on that same emission will not execute. If that is important in any particular case, the user must incorporate logic in the connected functions to cater for an exception causing only part execution, or must connect only one functor to any one signal and "chain" emissions by hand so as to do the right thing.