miso is short for micro signals and slots and, as the name suggests, it is an implementation of the well-known language construct largely popularized by Qt: The signals and slots mechanism [Wikipedia]. As the Wikipedia article suggests, the signal-slot construct is a short, concise and pain-free implementation of the Observer pattern, ie. it provides the possibility for objects (called observers) to be recipients of automatic notifications from objects (called subjects) upon a change of state, or any other event worthy of notification.
Qt signals and slots | |
|
Reasoning
So, you may ask, why another signal/slot implementation? Since we already have the granddaddy of them all, the Qt signal/slot implementation which, as presented in [Qt4SigSlot], is a very powerful mechanism invented just for this purpose and which was even further enhanced with Qt5’s new syntax for signals and slots [Qt5SigSlot].
Or we have the boost signal libraries [BoostSigSlot], which are another excellent implementation of the same mechanism for the users of the boost library.
And we also have other less well-known signal/slot implementations, such as Sarah Thompsons’ signal and slot library [sigslot-1] or the VDK signals and slots written in C++ 11 [VDK], GNOME’s own libsigc++ [libsigc++], the nano signal slot [nanosigslot], Patrick Hogans’ Signals [Hogan] or several fresher ones from github ([nod] [sigcxx] [sigslot-2] [cpp-signal]) or the more hidden ones, which I was not able to discover even with Google’s powerful search algorithm.
All these excellent pieces of software were written specifically for this purpose, and they all serve the needs of software developers wanting to use the signals and slots mechanism without too much hassle.
And on the other side, the Observer pattern is a very widely adopted and successful pattern which has also been widely studied in various articles, including Overload’s own ones, such as Phil Bass’s articles in Overload 52 and 53: ‘Implementing the Observer Pattern’ [Bass02] or Pete Goodliffe’s articles in Overload 37, 38 and 41 (‘Experiences of Implementing the Observer Design Pattern’) [Goodliffe00] – both excellent articles which were not backed up by the power of C++11’s syntax and standard library at the time … due to the fact they were written in the first years of this century – but also Alan Griffiths’ article from 2014 (‘Designing Observers in C++11’) [Griffiths14], which lifted this pattern into the modern age using C++11 constructs.
So with a good reason, you may ask why…
But please bear with me … the implementation of this mechanism seemed to be such an interesting and highly challenging research project that I could not resist it. I wanted to use the elegance of the constructs introduced with C++11 to avoid as much as possible the syntactical annoyances that I found in various signal/slot projects, which were bound to old-style C++ syntax, and I also wanted to keep this implementation short and concise. Hence, this header-only micro library appeared, and in the spirit of keeping it simple, it is under 150 lines, but still tries to offer the full functionality one would expect from a usable signal/slot library.
This article not only provides a good overview of the usage of and operations permitted by this tiny library, but also presents a few interesting C++11 techniques I have stumbled upon while implementing the library that I considered to be of sufficient calibre to be worth mentioning here.
The library itself
miso, being a single header library, is very easy to use. You just have to include the header file into your project and you’re good to go: #include <miso.h>
and from this point on you have access to the namespace miso
, which contains all the relevant declarations that you need to use it. Later in this article, we present all the important details of this namespace.
The library was written with portability and standard conformance in mind, and it is compilable for both Linux and Windows; it just needs a C++11 capable compiler.
Signals, slots, here and there
The notion of a slot is sort of uniform between all signal-slot libraries: It must be something that can be called. Regardless whether it’s a function, a functor, a lambda or some anomalous monstrosity returned by std::bind
and placed into a std::function
… at the end: It must be a callable. With or without parameters. Since this is what happens when you emit a signal: a ‘slot’ is called.
However, there is no real consensus regarding the very nature of signals. Qt adopted the most familiar, clear and easy to understand syntax of all the signatures:
signals: void signalToBeEmitted(float floatParameter, int intParameter);
Simple, and clean, just like a the definition of a member function, with a unique signature, representing the parameters this signal can pass to the slots when it is emit
ed. And the Qt meta object compiler takes care of it, by implementing the required supporting background operations (ie: the connection from the signal to actually calling the slot function), thus removing the burden from the programmer who can concentrate on implementing the actual functionality of the program.
The other big player in platform independent C++ library solutions, boost, on the other end has chosen a somewhat more complex approach to defining the same signal:
boost::signals2::signal<void (float, int)> sig;
This way of defining a signal feels very similar to the declaration of a function packed in a templated signal declaration and, because what it means is widely understood, it was adopted not only by [VDK], [neosigslot] and [nanosigslot] but also by nod, sigcxx, sigslot (the one from github) and cpp-signal. This syntax has the advantage of not requiring an extra step in the compilation phase (like moc
of Qt) since it is already syntactically correct C++ which the compiler can handle without too much hassle. This declaration also has the side effect that unless like Qt’s signal declaration, we have a tangible C++ variable which possibly is a class with methods and properties we can act upon.
Signals in miso
The signal definition of miso uses the following syntax in order to declare the same signal:
signal<float, int> float_int_sig;
Achieving the simplicity of Qt’s signal syntax seemed to not to be possible without using an extra step in the compilation phase (I am thinking of the convenience offered by moc
) and personally I have found including a function signature in the declaration of my signal not to work, so I went for the simplest syntax that was able to express the desired type of my signal (such as a signal
, having a float
and an int
parameter) and with the supporting help of the variadic templates introduced in C++11 this seemed to be the ideal combination. This syntax is also used by the library presented in [Hogan], with the difference being the name of the class and the fact that, in [Hogan], you need to specify a different class name based on the number of parameters.
So, from the above we see that a signal in the miso framework will be an object, constructed from a templated class which handles a various number of types. A signal which carries no extra information in the form of parameters must be declared as:
signal<> void_signal;
The design decision to not have to explicitly specify the void signal as a template specialization (ie: signal<void>
) has its advantages, both from the users’ point of view, and also the library’s internal design gained a bit of ruggedness from it.
A tiny application
The easiest way to introduce a new library is to present a small and simple example which showcases the basic usage of the library, so Listing 1 is the “Hello world†equivalent of miso.
#include "miso.h" #include <iostream> struct a_class { miso::signal<const char*> m_s; void say_hello() { emit m_s("Hello from a class"); } }; void a_function(const char* msg) { std::cout << msg << std::endl; } int main() { a_class a; miso::connect(a.m_s, a_function); a.say_hello(); } |
Listing 1 |
After skipping the mandatory inclusions, let’s analyze the important pieces:
Firstly, we declare a class (for now with the struct
keyword to keep the code short and uncluttered): struct a_class
. In the miso framework the signals belong to classes: it is not possible to have a signal living outside of an enclosing entity. This sort of resonates on the same frequency as Qt’s signal and slot mechanism; however, the boost signals are more independent and are not required to be bound to a class.
As mentioned above, the miso signals are to be bound to a class so now is the perfect time to declare the signal
object itself: miso::signal<const char*> m_s;
. All the miso types live in the miso
namespace in order to avoid global namespace pollution; however, this does not stop you from using the namespace as per your needs. The signal we have declared is expected to come with a parameter, which is of type const char*
.
The next line in the class is a plain method, which has just one role: to emit the signal. This is done with the intriguing line: emit m_s("Hello from a class");
. After spending several years with Qt, it just feel so natural to emit
a signal and since I wanted to keep the essence of the library close to already existing constructs to ease the transition, the emit
was born. emit
will be dissected later in the article to understand how it works.
The global method void a_function(const char* msg)
is the slot which is connected to this signal. It does not do very much; it only prints the message it receives from the signal to stdout, but for demonstration purposes this is acceptable.
And now we have reached to the main method of the application, which creates an object of type a_class
and connects its signal: a.m_s
to the global function a_function
. And, last but not least, the say_hello
method of the class is called, which in its turn will emit the signal. Upon emitting, the mechanism hidden in the library will kick in and the a_function
will be called. There is support in the library to obtain the object which emitted the signal the current slot is handling by calling the miso::sender
method; however, this is not presented in this short example.
This was a short example, now it is time to break down the application into tiny pieces, and start examining it.
The miso namespace
There are the following interesting elements in the miso
namespace
- The
signal
class - The
connect
and thesender
methods - The macro definition for
emit
. Although this is not namespace dependent, it just felt right to place it there.
There is also another namespace, called internal
with the intention that this is not to be used by the end-users.
Due to these being interconnected, I will present them one by one; however, be prepared for several jumps between various components, and since the namespace level entities use the internals very heavily it will be necessary to dig into them too.
The signal class
The class responsible for creating signals has the following declaration:
template <class... Args> class signal final
My intention was to keep the signal objects final, in order to have a clean interface and easy implementation; however, this does not stop you from removing the final
and providing good implementation for use cases for signal derived classes.
Since the class is a template class, nothing stops you from creating signals for your own data types and using them properly in the emit and the receiving slot declaration.
A short overview of the public members is as follows: The default constructor and destructors are marked default
, we just let the compiler do its default work.
The following two methods are connect
and disconnect
, and as their name suggest these will connect (or disconnect) the signal to (from) a slot.
Right now the following entities can be used as slots:
A function
The function must be declared with parameters corresponding to the parameters of the signal, and these parameters are not restricted only to basic C++ types. Using std::function
values also works, and so do the static
methods of various classes (see Listing 2).
struct other_class { void method() const { std::cout << "Hello from the Other class method"; } }; struct a_class { miso::signal<other_class> m_s; void say_hello() { emit m_s(other_class()); } }; void b_function(other_class oc) { oc.method(); } int main() { a_class a; std::function<void (other_class)> f = b_function; miso::connect(a.m_s, f); miso::connect(a.m_s, b_function); a.say_hello(); } |
Listing 2 |
The example in Listing 2 will call b_function
twice, which will print ‘Hello from the Other class method’ twice because it is connected twice to the same signal.
A lambda expression
The lambda expression can either be coming from an auto l = []() {...}
expression, or simply be written as a parameter to the connect
method. Again, correct matching of lambda parameters is required. So, an example for the above source code would be:
miso::connect(a.m_s, [](other_class b) { b.method(); });
A functor
A function object allows the instantiation of a functor class to be invoked in a manner similar to functions by providing an overload to operator ()
. So, in order to use a functor as a slot we can have the code in Listing 3.
struct functor { void operator()(int aa) { std::cout << "functor class's int slot:" << aa << std::endl; } }; struct a_class { miso::signal<int> m_s; void say_hello() { emit m_s(42); } }; int main() { a_class a; functor f; miso::connect(a.m_s, f); a.say_hello(); } |
Listing 3 |
As a side note, if there is more than one overload of operator()
it is possible to connect more than one signals to the same functor, each being handled by its own operator()
. And since this is an over-templated solution, the compiler will take care that slots with matching signatures to the ones the signal requires are actually available, otherwise it will spectacularly fail with a long list of cryptic messages.
Connect internals
In the signal
class,connect
and disconnect
are implemented both using internal::connect_i
, by calling it as shown in Listing 4, where the T&& f
is just the slot where we want this signal to reach upon emitting, and active
decides whether this signal is active or not (disconnect
calls the same function with active = false
).
template<class T> void connect(T&& f, bool active = true) { internal::connect_i<T, typename slot_holder<T>::FT, slot_holder<T>> (std::forward<T>(f), slot_holders, active); } |
Listing 4 |
The parameters to the internal function follow by using forward on the f
parameter to the current function, then slot_holders
which is a local variable of type:
std::vector<internal::common_slot_base*> slot_holders;
And finally, active
to tell the framework whether this signal is active or not (ie: should be called upon emit
or not).
Since common_slot_base
has appeared now, here is a definition for it:
struct common_slot_base { virtual ~common_slot_base() = default; };
so, basically it is just an interface to be used by all the different kinds of signals as a means of calling their corresponding slots. An immediate usage of it is in the signal
class:
struct slot_holder_base : public internal::common_slot_base { virtual void run_slots(Args... args) = 0; };
with further specialization following in Listing 5.
template<class T> struct slot_holder : public slot_holder_base { using FT = std::function<typename std::result_of<T(Args...)>::type(Args...)>; using slot_vec_type = std::vector<internal::func_and_bool<FT>>; slot_vec_type slots; void run_slots(Args... args) override { std::for_each(slots.begin(), slots.end(), [&](internal::func_and_bool<FT>& s) { if (s.active) (*(s.ft.get()))(args...); } ); } }; |
Listing 5 |
Reading the last method, it is obvious that the main action happens here, i.e. the actual call of a slot as per the corresponding signal takes place in these lines.
A bit more investigation of this structure gives us the declaration of FT
being an std::function
which at compile time identifies its return type from the template parameter of the slot_holder
class (T
which is supposed to be a ‘Callable’) which is fed into the std::result_of
of the <type_traits>
header having the parameters Args...
of the signal class that this slot_holder
resides in, combined again with the Args...
of the signal to obtain a fully understandable expression. Just a clarification, FT
stands for Function Type. And last but not least about this construct: Personally, I consider this piece of code to be one of the small wonders of the powers of modern C++... (read: even after writing it, and knowing that it’s syntactically correct and valid code, in my weaker moments I still wonder that it compiles...)
Since in this structure we have introduced a new structure (func_and_bool
), here is its definition:
template<typename FT> struct func_and_bool final { std::shared_ptr<FT> ft; bool active; void *addr; };
which roughly holds the lowest level of a slot, i.e.: a function object, whether it is active or not, and its address, thus revealing that at the lowest level all slots are decaying into an std::function
(the one which was declared in the type name FT
of the struct slot_holder
).
Now, that we have covered the necessary structures and functions of a signal, it is time to look at the actual function from the internals, which performs the real connect (see Listing 6).
template<class T, class FT, class SHT> void connect_i(T &&f, std::vector<common_slot_base *> &sholders, bool active = true) { static SHT sh; func_and_bool<FT> fb{ std::make_shared<FT>(std::forward<T>(f)), active, reinterpret_cast<void *>(&f) }; bool already_in = false; std::for_each(sh.slots.begin(), sh.slots.end(), [&](func_and_bool<FT> &s) { if (s.addr == fb.addr) { s.active = active; already_in = true; } } ); if (!already_in) { sh.slots.emplace_back(fb); } if (std::find(sholders.begin(), sholders.end(), static_cast<common_slot_base *>(&sh)) == sholders.end()) { sholders.push_back(&sh); } } |
Listing 6 |
So, dissecting it into bits we can observe the following:
- The type of the
static SHT sh;
local variable came in via the template parameters, and for our case it will have the structureslot_holder
declared in thesignal
class. Now, thissh
(slot holder, for the uninitiated) variable will be common for all theconnect_i
functions sharing a common prototype (hence, the static). For the perverse among you,SHT
stands for Slot Holder Type; don’t you dare start thinking of anything else. There is just one small drawback to using this static variable: miso in its current incarnation is not thread safe (so if someone is feeling tempted to fix this issue … feel free to make a pull request on github or depending on time and resources, the author might fix it). - The next step is to create a
func_and_bool
object with the type of theFT
we discussed in theslot_holder
class, and we check whether the newly created object is in the slot holder already (by comparing its physical address to those already in the container). If yes, we set its activeness state to the one required in the parameter, but since we don’t want to add it again, we also flip a boolean flag for later usage. - The next step is updating the incoming parameter
sholders
in order to append the localsh
object. This is where the magic happens since this parameter is the same that is declared in thesignal
class, and sinceslot_holder<T>
is acommon_slot_base
specialization we successfully managed to gather all the slots regardless of their parameters, this type of signal class is connected to into one common entity we can operate on.
With these covered we have successfully surveyed the mechanisms behind the connection of a slot to a specific signal, so we can jump to the next stage of our library, namely, emitting a signal.
Emitting a signal
The syntax, as seen from the tiny example application provided, is:
emit signalname(param1, param2, ...);
By digging further in the header file, we find that emit
basically is:
#define emit \ miso::internal::emitter\ <std::remove_pointer<decltype(this)>\ ::type>(*this) <<
(Yes, that is a <<
operator at the end of the line)
So, a simple emit
will create in its turn a temporary miso::internal::emitter
object, which is a helper class like Listing 7, whose role is to keep track of the current object that emitted the signal. I’m confident that the logic for this is covered in the nice self- explanatory code above, so let’s just give an example of how can we retrieve the sender of the current signal (see Listing 8).
template<class T> struct emitter final { explicit emitter(const T &emtr) { sender_objs.push(&emtr); minstance = this; } ~emitter() { sender_objs.pop(); minstance = nullptr; } static T *sender() { return const_cast<T *>(sender_objs.top()); } static emitter<T> *instance() { return minstance; } private: static std::stack<const T *> sender_objs; static emitter<T> *minstance; }; |
Listing 7 |
struct a_class { miso::signal<int> m_s; void say_hello() { emit m_s(42); } int x = 45; }; struct functor { void operator()(int aa) { std::cout << "functor class's int slot:" << aa << std::endl; a_class* ap = miso::sender<a_class>(); std::cout << "x in emitter class:" << ap->x << std::endl; } }; int main() { a_class a; functor f; miso::connect(a.m_s, f); a.say_hello(); } |
Listing 8 |
So, in the slot, we just simply call:
a_class* ap = miso::sender<a_class>();
and this gives us the type of the class that has emitted the signal resulting in us being in the current slot
. Be aware, that if we are not handling the slot class due to an emit from a signal, and we call the miso::sender
we will get a std::runtime_error
exception.
The internals of calling the signal handler
If you wonder about the <<
operator in the macro definition of emit
, please note the signal
class has a very complex friend declaration, in the form of:
template<class T, class... Brgs> friend internal::emitter<T> && internal::operator << (internal::emitter<T> &&e, signal<Brgs...> &s);
which looks like:
template<class T, class... Args> emitter<T> &&operator <<(internal::emitter<T> &&e, signal<Args...> &s) { s.delayed_dispatch(); return std::forward<internal::emitter<T>>(e); }
To make the syntax possible, please also note the following in the signal
class:
std::tuple<Args...> call_args; signal<Args...>& operator()(Args... args) { call_args = std::tuple<Args...>(args...); return *this; }
(so, the signal class in its turn is also a functor :) ) otherwise the required syntax for emit
wouldn’t have been possible. The call_args
member is nothing more than the calling arguments for emitting the signal populated by this operator()
.
Now we can see that the temporary emitter
object created above will call the overloaded <<
operator with the signal (which in its turn has already consumed the input parameters via the operator()
call) , and in there the delayed_dispatch
method of the signal is called.
When it comes to delayed dispatch, [Stackoverflow] shows us how to unpack a tuple holding various values of various types to a function with matching parameter types. This is necessary in order to have a perfect match between the values the signal’s call arguments were populated with and the slots that are supposed to get the same values.
When the delayed dispatch method runs, it in turn calls run_slots
from the slot holders vector (which, if you remember, were populated in the connect
step).
The future
With this lengthy overview, I am confident, that everyone needing to use a lightweight signal-slot library has at least one more choice to select from, making the decision even harder. At the same time, I’m hoping that this article has shed some light into how to use this library. Whether it is a good choice for your team or not that depends entirely on you.
The library
You can find the implementation of the library in miso.h (released under MIT license) at https://github.com/fritzone/miso
Also, please note: For now, this is far from a fully fledged signal-slot library, offering the power and functionality you would expect from Qt or Boost. Depending on time and resources, I would be happy to add features you request (or even approve your pull request in case you consider it worth fixing a few bugs here and there, or adding a new nice to have item to it) but till then kindly treat it lightly.
References
[Bass02] Phil Bass (2002) ‘Implementing the Observer Pattern in C++’ in Overload 52, https://accu.org/var/uploads/journals/overload52-FINAL.pdf, and Overload 53, https://accu.org/var/uploads/journals/overload53-FINAL.pdf
[BoostSigSlot] http://www.boost.org/doc/libs/1_61_0/doc/html/signals2.html
[cpp-signal] https://github.com/Montellese/cpp-signal
[Goodliffe00] Pete Goodliffe (2000) ‘Experiences of Implementing the Observer Design Pattern’ in Overload 37, https://accu.org/index.php/journals/488, Overload 38, https://accu.org/index.php/journals/481, and Overload 41, https://accu.org/index.php/journals/464
[Griffiths14] Alan Griffiths (2014) ‘Designing Observers in C++11’ in Overload 124, https://accu.org/var/uploads/journals/Overload124.pdf#page=5
[Hogan] https://github.com/pbhogan/Signals
[libsigc++] http://libsigc.sourceforge.net/
[nanosigslot] https://github.com/NoAvailableAlias/nano-signal-slot
[neosigslot] http://i42.co.uk/stuff/neosigslot.htm
[nod] https://github.com/fr00b0/nod
[Qt4SigSlot] C++ GUI Programming with Qt 4 by Jasmin Blanchette & Mark Summerfield, ISBN-13: 978-0131872493
[Qt5SigSlot] http://doc.qt.io/qt-5/signalsandslots.html
[QtMoc] http://doc.qt.io/qt-5/moc.html
[sigcxx] https://github.com/zhanggyb/sigcxx
[sigslot-1] sigslot, a signals and slots library written by Sarah Thompson, http://sigslot.sourceforge.net
[sigslot-2] https://github.com/supergrover/sigslot
[Stackoverflow] http://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer
[VDK] vdk-signals at http://vdksoft.github.io/signals/index.html
[Wikipedia]Â https://en.wikipedia.org/wiki/Signals_and_slots
Overload Journal #146 - August 2018 + Programming Topics
Browse in : |
All
> Journals
> Overload
> o146
(7)
All > Topics > Programming (877) Any of these categories - All of these categories |