emitter.h
Go to the documentation of this file.
/* Copyright (C) 2009 to 2013 Chris Vine
The library comprised in this file or of which this file is part is
distributed by Chris Vine under the GNU Lesser General Public
License as follows:
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License, version 2.1, for more details.
You should have received a copy of the GNU Lesser General Public
License, version 2.1, along with this library (see the file LGPL.TXT
which came with this source code package in the c++-gtk-utils
sub-directory); if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
However, it is not intended that the object code of a program whose
source code instantiates a template from this file or uses macros or
inline functions (of any length) should by reason only of that
instantiation or use be subject to the restrictions of use in the GNU
Lesser General Public License. With that in mind, the words "and
macros, inline functions and instantiations of templates (of any
length)" shall be treated as substituted for the words "and small
macros and small inline functions (ten lines or less in length)" in
the fourth paragraph of section 5 of that licence. This does not
affect any other reason why object code may be subject to the
restrictions in that licence (nor for the avoidance of doubt does it
affect the application of section 2 of that licence to modifications
of the source code in this file).
*/
#ifndef CGU_EMITTER_H
#define CGU_EMITTER_H
/**
* @file emitter.h
* @brief 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 function. 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.
*
* Alternatives
* ------------
*
* 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.
*
* Subsidiary thread-safety points
* -------------------------------
*
* 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. Another thread should therefore leave alone objects
* connected to a SafeEmitterArg object from the time of operator()()
* or emit() beginning to the time of it ending, and not try to
* interfere.
*
* 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.
*
* @anchor AssignmentAnchor
* Assignment
* ----------
*
* 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 in the class which has Releaser as a
* member and leave Releaser alone in the assignment operator.
*
* Usage
* -----
*
* These are examples:
*
* @code
* using namespace Cgu;
*
* Emitter e1;
* e1.connect([] () {std::cout << "Hello world\n";});
* e1();
*
* SafeEmitter se1;
* se1.connect([] () {std::cout << "Hello world\n";});
* se1();
*
* int res;
* EmitterArg<int, int&> e2;
* e2.connect([] (int i, int& j) {j = 10 * i;});
* e2(2, res);
* std::cout << "10 times 2 is " << res << '\n';
*
* SafeEmitterArg<int, int&> se2;
* se2.connect([] (int i, int& j) {j = 10 * i;});
* se2(2, res);
* std::cout << "10 times 2 is " << res << '\n';
* @endcode
*
* 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):
*
* @code
* using namespace Cgu;
*
* Something arg;
* SafeEmitterArg<int> se;
* se.connect(Callback::make_ref(my_obj, &MyClass::my_method, arg);
* se(5);
* @endcode
*
* 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).
*
* Exception safety
* ----------------
*
* 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.
*/
/*
Mutex locking heirarchy:
Some out-of-order locking must take place because of the
relationship between the Releaser and SafeEmitterArg<> classes. The
mutex of Releaser is given the higher priority. This means that a
plain EmitterArg<> object will not take any hit from the fact that
Releaser is also useable with SafeEmitterArg<> objects.
One consequence is that to avoid deadlocks, it is the
SafeEmitterArg<> functions which must yield when a deadlock would
otherwise arise. Yielding could occur in
SafeEmitterArg<>::~SafeEmitterArg() and
SafeEmitterArg<>::disconnect().
*/
#ifdef CGU_USE_SCHED_YIELD
#include <sched.h>
#else
#include <unistd.h>
#endif
#include <list>
#include <unordered_set>
#include <algorithm>
#include <functional>
#include <type_traits> // for std::remove_reference, std::enable_if and std::is_convertible
#include <c++-gtk-utils/callback.h>
#include <c++-gtk-utils/mutex.h>
#include <c++-gtk-utils/cgu_config.h>
namespace Cgu {
/* The four basic emitter types */
template <class... FreeArgs> class EmitterArg;
template <class... FreeArgs> class SafeEmitterArg;
typedef EmitterArg<> Emitter;
typedef SafeEmitterArg<> SafeEmitter;
/**
* @class Releaser emitter.h c++-gtk-utils/emitter.h
* @brief A class used for tracking EmitterArg and SafeEmitterArg
* connections.
* @sa EmitterArg SafeEmitterArg
* @sa emitter.h
* @sa Callback namespace
*
* This class provides tracking of EmitterArg and SafeEmitterArg
* connections. It should be a public member of any target class
* which wants functors representing any of its methods to be
* disconnected automatically from an EmitterArg or SafeEmitterArg
* object when the target class object is destroyed, and is passed as
* one of the arguments to the connect() method of EmitterArg or
* SafeEmitterArg.
*
* All its methods are thread-safe.
*
* For further background, read this: emitter.h
*/
class Releaser {
// from version 2.0.0-rc3 we use std::unordered_set rather than
// std::list in Releaser. We can't do that for
// EmitterArg/SafeEmitterArg objects, as they need to execute
// connected functors in the order in which they were connected.
std::unordered_set<Callback::SafeFunctor> disconnect_set;
Thread::Mutex mutex;
// only an EmitterArg or SafeEmitterArg object can access add(), remove and try_remove()
void add(const Callback::SafeFunctor&);
void remove(const Callback::SafeFunctor&);
void try_remove(const Callback::SafeFunctor&, int*);
public:
template <class... T> friend class EmitterArg;
template <class... T> friend class SafeEmitterArg;
// operator=() and the copy constructor should copy nothing from the
// assignor, because disconnect_set should be empty in the
// assignee, as any class containing us does not acquire as assignee
// any emitter functors representing any of its methods
/**
* See notes on @ref AssignmentAnchor "assignment" to see how this
* operates. This does not throw provided that the destructors of any
* bound arguments of a functor managed by this Releaser object prior
* to assignment do not throw (as they should not do), and assuming
* that merely iterating through a list does not throw (as it would
* not on any sane implementation).
* @param r The assignee.
*/
Releaser& operator=(const Releaser& r);
/**
* This does not copy anything from the Releaser object passed as an
* argument - see the notes on @ref AssignmentAnchor "assignment" for
* an explanation of why. This does not throw.
* @param r A Releaser object.
* @exception std::bad_alloc This constructor might throw
* std::bad_alloc if memory is exhausted and the system throws in that
* case.
* @exception Thread::MutexError This constructor might throw
* Thread::MutexError if initialisation of the contained mutex fails.
* (It is often not worth checking for this, as it means either memory
* is exhausted or pthread has run out of other resources to create
* new mutexes.)
*/
Releaser(const Releaser& r) {}
/**
* @exception std::bad_alloc The default constructor might throw
* std::bad_alloc if memory is exhausted and the system throws in that
* case.
* @exception Thread::MutexError The default constructor might throw
* Thread::MutexError if initialisation of the contained mutex fails.
* (It is often not worth checking for this, as it means either memory
* is exhausted or pthread has run out of other resources to create
* new mutexes.)
*/
Releaser() = default;
/**
* The destructor does not throw provided that the destructors of any
* bound arguments of a functor managed by this Releaser object do not
* throw (as they should not do), and assuming that merely iterating
* through an unordered_set does not throw (as it would not on any
* sane implementation).
*/
~Releaser();
/* Only has effect if --with-glib-memory-slices-compat or
* --with-glib-memory-slices-no-compat option picked */
CGU_GLIB_MEMORY_SLICES_FUNCS
};
/* the emitter classes */
/**
* @class EmitterArg emitter.h c++-gtk-utils/emitter.h
* @brief A class to execute callbacks connected to it, with provision
* for automatic disconnection.
* @sa SafeEmitterArg Releaser
* @sa emitter.h
* @sa Callback namespace
*
* Callable objects (such as lambda expressions or the return value of
* std::bind) or Callback::FunctorArg objects may be connected to
* Emitter classes, and will be executed when EmitterArg::emit() or
* EmitterArg::operator()() are called.
*
* One version of the connect() method takes a Releaser object as an
* argument. Such a Releaser object should be a public member of any
* target class which wants functors representing (or calling into)
* any of its methods to be disconnected automatically from the
* EmitterArg object when the target class object is destroyed.
*
* A connection may be explicitly disconnected by calling the
* disconnect() method, and may also be temporarily blocked and
* subsequently unblocked with the block() and unblock() methods.
*
* The template types are the types of the unbound arguments, if any.
* EmitterArg<> is typedef'ed to Emitter.
*
* @b Usage
*
* These are examples:
*
* @code
* using namespace Cgu;
*
* Emitter e1;
* e1.connect([] () {std::cout << "Hello world\n";});
* e1();
*
* int res;
* EmitterArg<int, int&> e2;
* e2.connect([] (int i, int& j) {j = 10 * i;});
* e2(2, res);
* std::cout << "10 times 2 is " << res << '\n';
* @endcode
*
* Callback::FunctorArg 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):
*
* @code
* using namespace Cgu;
*
* Something arg;
* EmitterArg<int> e;
* e.connect(Callback::make_ref(my_obj, &MyClass::my_method, arg);
* e(5);
* @endcode
*
* For further background, including about thread-safety and exception
* safety and other matters, read this: emitter.h, or for more
* information about bound and unbound arguments, read this:
* Cgu::Callback.
*/
template <class... FreeArgs>
class EmitterArg {
#ifndef DOXYGEN_PARSING
// f1 is the functor we execute when we emit()
// f2 is the functor we execute in our destructor if we are destroyed
// before the remote object is
struct ListItem {
Callback::FunctorArg<FreeArgs...> f1;
Callback::Functor f2;
bool blocked;
ListItem(Callback::FunctorArg<FreeArgs...> f1_, Callback::Functor f2_):
f1(f1_), f2(f2_), blocked(false) {}
};
#endif
std::list<ListItem> emission_list;
// only Releaser objects can access this
void tracking_disconnect(const Callback::FunctorArg<FreeArgs...>&);
public:
friend class Releaser;
/**
* This will execute the connected functors.
* @param args The unbound arguments to be passed to the referenced
* function or class method, if any.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. In
* addition, it will throw if the functions or class methods
* referenced by the functors throw (or if the copy constructor of a
* free or bound argument throws and it is not a reference argument).
*/
void operator()(typename Cgu::Param<FreeArgs>::ParamType... args) const {emit(args...);}
/**
* This will execute the connected functors.
* @param args The unbound arguments to be passed to the referenced
* function or class method, if any.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. In
* addition, it will throw if the functions or class methods
* referenced by the functors throw (or if the copy constructor of a
* free or bound argument throws and it is not a reference argument).
*/
void emit(typename Cgu::Param<FreeArgs>::ParamType... args) const;
/**
* This will execute the connected functors, but it also reports
* whether in fact there were any connected functors to execute. (It
* is not necessary to use this function just because it is not known
* whether a functor is connected - if the standard emit() function is
* called when no functor is connected, nothing will happen. The
* feature of this method is that it will report the outcome.)
* @param args The unbound arguments to be passed to the connected
* functions or class methods, if any.
* @return Returns false if there were no functors to execute, or true
* if functors have been executed.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. In
* addition, it will throw if the functions or class methods
* referenced by the functors throw (or if the copy constructor of a
* free or bound argument throws and it is not a reference argument).
*/
bool test_emit(typename Cgu::Param<FreeArgs>::ParamType... args) const;
/**
* Connects a Callback::FunctorArg object.
* @param f The Callback::FunctorArg object to connect.
* @return The Callback::FunctorArg object connected.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case.
*/
Callback::FunctorArg<FreeArgs...> connect(const Callback::FunctorArg<FreeArgs...>& f);
/**
* Connects a Callback::FunctorArg object.
* @param f The Callback::FunctorArg object to connect.
* @param r A Releaser object for automatic disconnection of the
* Callback::FunctorArg object if the object whose method it
* represents is destroyed.
* @return The Callback::FunctorArg object connected.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case.
*/
Callback::FunctorArg<FreeArgs...> connect(const Callback::FunctorArg<FreeArgs...>& f, Releaser& r);
/**
* Connects a callable object, such as formed by a lambda expression
* or the result of std::bind.
* @param f The callable object to connect. If must have the same
* unbound argument types as the EmitterArg object concerned.
* @return A Callback::FunctorArg object which can be passed to
* disconnect(), block() or unblock().
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. If might
* also throw if the copy or move constructor of the callable object
* throws.
*
* Since 2.1.0
*/
// we need to use enable_if so that where this function is passed a
// Callback::SafeFunctorArg object or a pointer to a
// Callback::Callback object or some other convertible object, this
// templated overload is dropped from the overload set, in order to
// support the deprecated overloads of this function.. This overload
// calls into the version of this function taking a
// Callback::Functor object in order to perform type erasure.
template <class F,
class = typename std::enable_if<!std::is_convertible<typename std::remove_reference<F>::type,
const Callback::FunctorArg<FreeArgs...>>::value>::type>
Callback::FunctorArg<FreeArgs...> connect(F&& f) {
return connect(Callback::lambda<FreeArgs...>(std::forward<F>(f)));
}
/**
* Connects a callable object, such as formed by a lambda expression
* or the result of std::bind.
* @param f The callable object to connect. If must have the same
* unbound argument types as the EmitterArg object concerned.
* @param r A Releaser object for automatic disconnection of the
* callable object if an object whose method it represents or calls
* into is destroyed.
* @return A Callback::FunctorArg object which can be passed to
* disconnect(), block() or unblock().
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. If might
* also throw if the copy or move constructor of the callable object
* throws.
*
* Since 2.1.0
*/
// we need to use enable_if so that where this function is passed a
// Callback::SafeFunctorArg object or a pointer to a
// Callback::Callback object or some other convertible object, this
// templated overload is dropped from the overload set, in order to
// support the deprecated overloads of this function.. This overload
// calls into the version of this function taking a
// Callback::Functor object in order to perform type erasure.
template <class F,
class = typename std::enable_if<!std::is_convertible<typename std::remove_reference<F>::type,
const Callback::FunctorArg<FreeArgs...>>::value>::type>
Callback::FunctorArg<FreeArgs...> connect(F&& f, Releaser& r) {
return connect(Callback::lambda<FreeArgs...>(std::forward<F>(f)), r);
}
/**
* Disconnects a functor previously connected. This does not throw
* provided that the destructors of any bound arguments do not throw
* (as they should not do), and assuming that merely iterating through
* a list does not throw (as it would not on any sane implementation).
* @param f The functor to disconnect.
* @note If the same functor has been connected more than once to the
* same EmitterArg object, this call will disconnect all of them.
*/
void disconnect(const Callback::FunctorArg<FreeArgs...>& f);
/**
* Blocks a connected functor from executing when emit() or
* operator()() is called until unblock() is called. This method does
* not throw (assuming that merely iterating through a list does not
* throw, as it would not on any sane implementation).
* @param f The functor to block.
* @note If the same functor has been connected more than once to the
* same EmitterArg object, this call will block all of them.
*/
void block(const Callback::FunctorArg<FreeArgs...>& f);
/**
* Unblocks a previously blocked functor. This method does not throw
* (assuming that merely iterating through a list does not throw, as
* it would not on any sane implementation).
* @param f The functor to unblock.
* @note If the same functor has been connected more than once to the
* same EmitterArg object, this call will unblock all of them.
*/
void unblock(const Callback::FunctorArg<FreeArgs...>& f);
/**
* @exception std::bad_alloc The constructor might throw
* std::bad_alloc if memory is exhausted and the system throws in that
* case.
*/
EmitterArg() = default;
/**
* This class cannot be copied. The copy constructor is deleted.
*/
EmitterArg(const EmitterArg&) = delete;
/**
* This class cannot be copied. The assignment operator is deleted.
*/
EmitterArg& operator=(const EmitterArg&) = delete;
/**
* The destructor does not throw provided that the destructors of any
* bound arguments do not throw (as they should not do), and assuming
* that merely iterating through a list does not throw (as it would
* not on any sane implementation).
*/
~EmitterArg();
/* Only has effect if --with-glib-memory-slices-compat or
* --with-glib-memory-slices-no-compat option picked */
CGU_GLIB_MEMORY_SLICES_FUNCS
};
template <class... FreeArgs>
EmitterArg<FreeArgs...>::~EmitterArg() {
for(const auto& l: emission_list) {l.f2();}
}
template <class... FreeArgs>
void EmitterArg<FreeArgs...>::emit(typename Cgu::Param<FreeArgs>::ParamType... args) const {
// create a local copy of emission_list, to enable 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 function 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 function tries to block, unblock or disconnect another
// function 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 function may not destroy an object whose non-static
// method is 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).
// we can't use uniform initialisation here as it would be
// construed as invoking an initialiser list with a list item,
// rather than passing an already formed list
std::list<ListItem> local_list = emission_list;
for (const auto& l: local_list) {
if (!l.blocked) l.f1(args...);
}
}
template <class... FreeArgs>
bool EmitterArg<FreeArgs...>::test_emit(typename Cgu::Param<FreeArgs>::ParamType... args) const {
if (emission_list.empty()) return false;
emit(args...);
return true;
}
template <class... FreeArgs>
Callback::FunctorArg<FreeArgs...> EmitterArg<FreeArgs...>::connect(const Callback::FunctorArg<FreeArgs...>& f1) {
emission_list.push_back(ListItem{f1, Callback::Functor()});
return f1;
}
template <class... FreeArgs>
Callback::FunctorArg<FreeArgs...> EmitterArg<FreeArgs...>::connect(const Callback::FunctorArg<FreeArgs...>& f1, Releaser& r) {
// In this method:
// f1 is the functor we execute when we emit()
// f2 is the functor we execute in our destructor if we are destroyed before the
// remote object is
// f3 is the functor the remote object executes in its Releaser if it is destroyed
// before we are, or if Releaser::operator=() is called
Callback::SafeFunctor f3{Callback::make_ref(*this, &EmitterArg<FreeArgs...>::tracking_disconnect, f1)};
Callback::Functor f2{Callback::make_ref(r, &Releaser::remove, f3)};
r.add(f3);
try {
emission_list.push_back(ListItem{f1, f2});
}
catch (...) {
r.remove(f3);
throw;
}
return f1;
}
template <class... FreeArgs>
void EmitterArg<FreeArgs...>::disconnect(const Callback::FunctorArg<FreeArgs...>& arg) {
// in theory, we could have connected the same functor object
// more than once, so cater for that
auto iter = emission_list.begin();
for (;;) {
iter = std::find_if(iter, emission_list.end(),
[&arg](const ListItem& p) -> bool {return p.f1 == arg;});
if (iter != emission_list.end()) {
// remove ourselves from the remote Releaser object
(iter->f2)();
// remove this item from emission_list
iter = emission_list.erase(iter);
}
else break;
}
}
// tracking disconnect() is the same as disconnect(), except that we do not
// execute f2 as the remote Releaser object will destroy its own functors
// in that case
template <class... FreeArgs>
void EmitterArg<FreeArgs...>::tracking_disconnect(const Callback::FunctorArg<FreeArgs...>& arg) {
auto iter = emission_list.begin();
for (;;) {
iter = std::find_if(iter, emission_list.end(),
[&arg](const ListItem& p) -> bool {return p.f1 == arg;});
if (iter != emission_list.end()) {
// remove this item from emission_list
iter = emission_list.erase(iter);
}
else break;
}
}
template <class... FreeArgs>
void EmitterArg<FreeArgs...>::block(const Callback::FunctorArg<FreeArgs...>& arg) {
// in theory, we could have connected the same functor object
// more than once, so cater for that
auto iter = emission_list.begin();
for (;;) {
iter = std::find_if(iter, emission_list.end(),
[&arg](const ListItem& p) -> bool {return p.f1 == arg;});
if (iter != emission_list.end()) {
iter->blocked = true;
++iter;
}
else break;
}
}
template <class... FreeArgs>
void EmitterArg<FreeArgs...>::unblock(const Callback::FunctorArg<FreeArgs...>& arg) {
// in theory, we could have connected the same functor object
// more than once, so cater for that
auto iter = emission_list.begin();
for (;;) {
iter = std::find_if(iter, emission_list.end(),
[&arg](const ListItem& p) -> bool {return p.f1 == arg;});
if (iter != emission_list.end()) {
iter->blocked = false;
++iter;
}
else break;
}
}
/**
* @class SafeEmitterArg emitter.h c++-gtk-utils/emitter.h
* @brief A thread-safe class to execute callbacks connected to it,
* with provision for automatic disconnection.
* @sa EmitterArg Releaser
* @sa emitter.h
* @sa Callback namespace
*
* This is a thread-safe version of the EmitterArg class. Callable
* objects (such as lambda expressions or the return value of
* std::bind) or Callback::SafeFunctorArg objects may be connected to
* SafeEmitter classes (referred to be below as "connected
* callables"), and will be executed when SafeEmitterArg::emit() or
* SafeEmitterArg::operator()() are called.
*
* One version of the connect() method takes a Releaser object as an
* argument. Such a Releaser object should be a public member of any
* target class which wants connected callables representing (or
* calling into) any of its methods to be disconnected automatically
* from the SafeEmitterArg object when the target class object is
* destroyed.
*
* A connection may be explicitly disconnected by calling the
* disconnect() method, and may also be temporarily blocked and
* subsequently unblocked with the block() and unblock() methods.
*
* The template types are the types of the unbound arguments, if any.
* SafeEmitterArg<> is typedef'ed to SafeEmitter.
*
* @b Usage
*
* These are examples:
*
* @code
* using namespace Cgu;
*
* SafeEmitter se1;
* se1.connect([] () {std::cout << "Hello world\n";});
* se1();
*
* int res;
* SafeEmitterArg<int, int&> se2;
* se2.connect([] (int i, int& j) {j = 10 * i;});
* se2(2, res);
* std::cout << "10 times 2 is " << res << '\n';
* @endcode
*
* 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):
*
* @code
* using namespace Cgu;
*
* Something arg;
* SafeEmitterArg<int> se;
* se.connect(Callback::make_ref(my_obj, &MyClass::my_method, arg);
* se(5);
* @endcode
*
* For further background, including about thread-safety and exception
* safety and other matters, read this: emitter.h, or for more
* information about bound and unbound arguments, read this:
* Cgu::Callback.
*/
template <class... FreeArgs>
class SafeEmitterArg {
#ifndef DOXYGEN_PARSING
// f1 is the functor we execute when we emit()
// f2 is the functor we execute in our destructor if we are destroyed
// before the remote object is
struct ListItem {
Callback::SafeFunctorArg<FreeArgs...> f1;
Callback::SafeFunctorArg<int*> f2;
bool blocked;
ListItem(Callback::SafeFunctorArg<FreeArgs...> f1_, Callback::SafeFunctorArg<int*> f2_):
f1(f1_), f2(f2_), blocked(false) {}
};
#endif
std::list<ListItem> emission_list;
mutable Thread::Mutex mutex; // make this mutable so we can lock/unlock in const methods
// only Releaser objects can access this
void tracking_disconnect(const Callback::SafeFunctorArg<FreeArgs...>&);
public:
friend class Releaser;
/**
* This will execute the connected functors. It is thread safe if the
* functions or class methods referenced by the connected functors are
* thread safe.
* @param args The unbound arguments to be passed to the referenced
* function or class method, if any.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. In
* addition, it will throw if the functions or class methods
* referenced by the functors throw (or if the copy constructor of a
* free or bound argument throws and it is not a reference argument).
*/
void operator()(typename Cgu::Param<FreeArgs>::ParamType... args) const {emit(args...);}
/**
* This will execute the connected functors. It is thread safe if the
* functions or class methods referenced by the connected functors are
* thread safe.
* @param args The unbound arguments to be passed to the referenced
* function or class method, if any.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. In
* addition, it will throw if the functions or class methods
* referenced by the functors throw (or if the copy constructor of a
* free or bound argument throws and it is not a reference argument).
*/
void emit(typename Cgu::Param<FreeArgs>::ParamType... args) const;
/**
* This will execute the connected functors, but it also reports
* whether in fact there were any connected functors to execute. It
* is thread safe if the functions or class methods referenced by the
* connected functors are thread safe. (It is not necessary to use
* this function just because it is not known whether a functor is
* connected - if the standard emit() function is called when no
* functor is connected, nothing will happen. The feature of this
* method is that it will report the outcome.)
* @param args The unbound arguments to be passed to the referenced
* function or class method, if any.
* @return Returns false if there were no functors to execute, or true
* if functors have been executed.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. In
* addition, it will throw if the functions or class methods
* referenced by the functors throw (or if the copy constructor of a
* free or bound argument throws and it is not a reference argument).
*/
bool test_emit(typename Cgu::Param<FreeArgs>::ParamType... args) const;
/**
* Connects a Callback::SafeFunctorArg object.
* @param f The Callback::SafeFunctorArg object to connect.
* @return The Callback::SafeFunctorArg object connected.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case.
*/
Callback::SafeFunctorArg<FreeArgs...> connect(const Callback::SafeFunctorArg<FreeArgs...>& f);
/**
* Connects a Callback::SafeFunctorArg object.
* @param f The Callback::SafeFunctorArg object to connect.
* @param r A Releaser object for automatic disconnection of the
* Callback::SafeFunctorArg object if the object whose method it
* represents is destroyed.
* @return The Callback::SafeFunctorArg object connected.
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case.
*/
Callback::SafeFunctorArg<FreeArgs...> connect(const Callback::SafeFunctorArg<FreeArgs...>& f, Releaser& r);
/**
* Connects a callable object, such as formed by a lambda expression
* or the result of std::bind.
* @param f The callable object to connect. If must have the same
* unbound argument types as the SafeEmitterArg object concerned.
* @return A Callback::SafeFunctorArg object which can be passed to
* disconnect(), block() or unblock().
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. If might
* also throw if the copy or move constructor of the callable object
* throws.
*
* Since 2.1.0
*/
// we need to use enable_if so that where this function is passed a
// Callback::SafeFunctorArg object or a pointer to a
// Callback::Callback object or some other convertible object, this
// templated overload is dropped from the overload set, in order to
// support the Callback::SafeFunctorArg overloads of this function.
// This overload calls into the version of this function taking a
// Callback::SafeFunctor object in order to perform type erasure.
template <class F,
class = typename std::enable_if<!std::is_convertible<typename std::remove_reference<F>::type,
const Callback::SafeFunctorArg<FreeArgs...>>::value>::type>
Callback::SafeFunctorArg<FreeArgs...> connect(F&& f) {
return connect(Callback::lambda<FreeArgs...>(std::forward<F>(f)));
}
/**
* Connects a callable object, such as formed by a lambda expression
* or the result of std::bind.
* @param f The callable object to connect. If must have the same
* unbound argument types as the SafeEmitterArg object concerned.
* @param r A Releaser object for automatic disconnection of the
* callable object if an object whose method it represents or calls
* into is destroyed.
* @return A Callback::SafeFunctorArg object which can be passed to
* disconnect(), block() or unblock().
* @exception std::bad_alloc The method might throw std::bad_alloc if
* memory is exhausted and the system throws in that case. If might
* also throw if the copy or move constructor of the callable object
* throws.
*
* Since 2.1.0
*/
// we need to use enable_if so that where this function is passed a
// Callback::SafeFunctorArg object or a pointer to a
// Callback::Callback object or some other convertible object, this
// templated overload is dropped from the overload set, in order to
// support the Callback::SafeFunctorArg overloads of this function.
// This overload calls into the version of this function taking a
// Callback::SafeFunctor object in order to perform type erasure.
template <class F,
class = typename std::enable_if<!std::is_convertible<typename std::remove_reference<F>::type,
const Callback::SafeFunctorArg<FreeArgs...>>::value>::type>
Callback::SafeFunctorArg<FreeArgs...> connect(F&& f, Releaser& r) {
return connect(Callback::lambda<FreeArgs...>(std::forward<F>(f)), r);
}
/**
* Disconnects a functor previously connected. This does not throw
* provided that the destructors of any bound arguments do not throw
* (as they should not do), and assuming that merely iterating through
* a list does not throw (as it would not on any sane implementation).
* It is thread safe.
* @param f The functor to disconnect.
* @note If the same functor has been connected more than once to the
* same SafeEmitterArg object, this call will disconnect all of them.
*/
void disconnect(const Callback::SafeFunctorArg<FreeArgs...>& f);
/**
* Blocks a connected functor from executing when emit() or
* operator()() is called until unblock() is called. This method does
* not throw (assuming that merely iterating through a list does not
* throw, as it would not on any sane implementation). It is thread
* safe.
* @param f The functor to block.
* @note If the same functor has been connected more than once to the
* same SafeEmitterArg object, this call will block all of them.
*/
void block(const Callback::SafeFunctorArg<FreeArgs...>& f);
/**
* Unblocks a previously blocked functor. This method does not throw
* (assuming that merely iterating through a list does not throw, as
* it would not on any sane implementation). It is thread safe.
* @param f The functor to unblock.
* @note If the same functor has been connected more than once to the
* same SafeEmitterArg object, this call will unblock all of them.
*/
void unblock(const Callback::SafeFunctorArg<FreeArgs...>& f);
/**
* @exception std::bad_alloc The constructor might throw
* std::bad_alloc if memory is exhausted and the system throws in that
* case.
* @exception Thread::MutexError The constructor might throw
* Thread::MutexError if initialisation of the contained mutex fails.
* (It is often not worth checking for this, as it means either memory
* is exhausted or pthread has run out of other resources to create
* new mutexes.)
*/
SafeEmitterArg() = default;
/**
* This class cannot be copied. The copy constructor is deleted.
*/
SafeEmitterArg(const SafeEmitterArg&) = delete;
/**
* This class cannot be copied. The assignment operator is deleted.
*/
SafeEmitterArg& operator=(const SafeEmitterArg&) = delete;
/**
* The destructor does not throw provided that the destructors of any
* bound arguments do not throw (as they should not do), and assuming
* that merely iterating through a list does not throw (as it would
* not on any sane implementation). It is thread-safe as regards the
* dropping of any connected functors and of any relevant Releaser
* objects.
*/
~SafeEmitterArg();
/* Only has effect if --with-glib-memory-slices-compat or
* --with-glib-memory-slices-no-compat option picked */
CGU_GLIB_MEMORY_SLICES_FUNCS
};
template <class... FreeArgs>
SafeEmitterArg<FreeArgs...>::~SafeEmitterArg() {
// go through emission_list() item by item, popping off the front and erasing
// as we go in case Releaser::try_remove() fails to acquire the lock on one
// of the iterations
Thread::Mutex::Lock lock{mutex};
while (!emission_list.empty()) {
auto iter = emission_list.begin();
int result = 0; // f2 might be a no-op
// remove ourselves from the remote Releaser object
(iter->f2)(&result);
if (!result) { // we got the Releaser mutex lock or no-op
// now remove this item from emission_list
emission_list.erase(iter);
}
else {
mutex.unlock();
// spin nicely
#ifdef CGU_USE_SCHED_YIELD
sched_yield();
#else
usleep(10);
#endif
mutex.lock();
}
}
}
template <class... FreeArgs>
void SafeEmitterArg<FreeArgs...>::emit(typename Cgu::Param<FreeArgs>::ParamType... args) const {
// create a local copy of emission_list, to enable 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 function 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 function tries to block, unblock or disconnect another
// function 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 function may not destroy an object whose non-static
// method is 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).
// SafeFunctorArg usage has the additional point that while an
// emission is in course, another thread should not try to do any of
// those things, or the same outcome will result. Another thread
// should leave alone objects connected to a SafeEmitterArg<> object
// from the time of operator()() or emit() beginning to the time of
// it ending, and not try to interfere.
// a side effect of having a local list is that, as required, we
// will not be holding our mutex when executing the functors it
// contains. It is OK having the functors in two different lists
// which are potentially (when our mutex is released) in two
// different threads, because the functors hold their
// Callback::Callback objects by SharedLockPtr so their reference
// count is protected (they are SafeFunctorArg<> functors).
std::list<ListItem> local_list;
{ // scope block for mutex lock
Thread::Mutex::Lock lock{mutex};
local_list = emission_list;
}
for (const auto& l: local_list) {
if (!l.blocked) l.f1(args...);
}
}
template <class... FreeArgs>
bool SafeEmitterArg<FreeArgs...>::test_emit(typename Cgu::Param<FreeArgs>::ParamType... args) const {
std::list<ListItem> local_list;
{ // scope block for mutex lock
Thread::Mutex::Lock lock{mutex};
if (emission_list.empty()) return false;
local_list = emission_list;
}
for (const auto& l: local_list) {
if (!l.blocked) l.f1(args...);
}
return true;
}
template <class... FreeArgs>
Callback::SafeFunctorArg<FreeArgs...> SafeEmitterArg<FreeArgs...>::connect(const Callback::SafeFunctorArg<FreeArgs...>& f1) {
Thread::Mutex::Lock lock{mutex};
emission_list.push_back(ListItem{f1, Callback::SafeFunctorArg<int*>()});
return f1;
}
template <class... FreeArgs>
Callback::SafeFunctorArg<FreeArgs...> SafeEmitterArg<FreeArgs...>::connect(const Callback::SafeFunctorArg<FreeArgs...>& f1, Releaser& r) {
// In this method:
// f1 is the functor we execute when we emit()
// f2 is the functor we execute in our destructor if we are destroyed before the
// remote object is
// f3 is the functor the remote object executes in its Releaser if it is destroyed
// before we are, or if Releaser::operator=() is called
Callback::SafeFunctor f3{Callback::make_ref(*this, &SafeEmitterArg<FreeArgs...>::tracking_disconnect, f1)};
Callback::SafeFunctorArg<int*> f2{Callback::make_ref(r, &Releaser::try_remove, f3)};
// we can't call Releaser::add() when holding our mutex or we will
// get out of order locking, as Releaser's mutex is acquired in that
// method, and we don't need to do so
r.add(f3);
Thread::Mutex::Lock lock{mutex};
try {
emission_list.push_back(ListItem{f1, f2});
}
catch (...) {
mutex.unlock();
r.remove(f3);
mutex.lock();
throw;
}
return f1;
}
template <class... FreeArgs>
void SafeEmitterArg<FreeArgs...>::disconnect(const Callback::SafeFunctorArg<FreeArgs...>& arg) {
// in theory, we could have connected the same functor object more than
// once, so cater for that as well as Releaser::try_remove() failing
Thread::Mutex::Lock lock{mutex};
auto iter = emission_list.begin();
for(;;) {
// gcc-4.4 doesn't support lambdas:
iter = std::find_if(iter, emission_list.end(),
[&arg](const ListItem& p) -> bool {return p.f1 == arg;});
if (iter != emission_list.end()) {
int result = 0; // f2 might be a no-op
// remove ourselves from the remote Releaser object
(iter->f2)(&result);
if (!result) { // we got the Releaser mutex lock or no-op
// now remove this item from emission_list
iter = emission_list.erase(iter);
}
else {
mutex.unlock();
// spin nicely
#ifdef CGU_USE_SCHED_YIELD
sched_yield();
#else
usleep(10);
#endif
mutex.lock();
// start again at the beginning - we have released the mutex
// so our iterator may have become invalid
iter = emission_list.begin();
}
}
else break;
}
}
// tracking disconnect() is the same as disconnect(), except that we do not
// execute f2 as the remote Releaser object will destroy its own functors
// in that case
template <class... FreeArgs>
void SafeEmitterArg<FreeArgs...>::tracking_disconnect(const Callback::SafeFunctorArg<FreeArgs...>& arg) {
Thread::Mutex::Lock lock{mutex};
auto iter = emission_list.begin();
for (;;) {
iter = std::find_if(iter, emission_list.end(),
[&arg](const ListItem& p) -> bool {return p.f1 == arg;});
if (iter != emission_list.end()) {
// remove this item from emission_list
iter = emission_list.erase(iter);
}
else break;
}
}
template <class... FreeArgs>
void SafeEmitterArg<FreeArgs...>::block(const Callback::SafeFunctorArg<FreeArgs...>& arg) {
// in theory, we could have connected the same functor object
// more than once, so cater for that
Thread::Mutex::Lock lock{mutex};
auto iter = emission_list.begin();
for (;;) {
iter = std::find_if(iter, emission_list.end(),
[&arg](const ListItem& p) -> bool {return p.f1 == arg;});
if (iter != emission_list.end()) {
iter->blocked = true;
++iter;
}
else break;
}
}
template <class... FreeArgs>
void SafeEmitterArg<FreeArgs...>::unblock(const Callback::SafeFunctorArg<FreeArgs...>& arg) {
// in theory, we could have connected the same functor object
// more than once, so cater for that
Thread::Mutex::Lock lock{mutex};
auto iter = emission_list.begin();
for (;;) {
iter = std::find_if(iter, emission_list.end(),
[&arg](const ListItem& p) -> bool {return p.f1 == arg;});
if (iter != emission_list.end()) {
iter->blocked = false;
++iter;
}
else break;
}
}
} // namespace Cgu
#endif // EMITTER_H