miso: Micro Signal/Slot Implementation

miso: Micro Signal/Slot Implementation

By Deák Ferenc

Overload, 26(146):8-13, August 2018


The Observer pattern has many existing implementations. Deák Ferenc presents a new implementation using modern C++ techniques.

miso is short for mi cro s ignals and sl o ts 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

For those who haven’t had the chance to work with Qt’s signals and slots, a small note: Qt has a handy tool, called moc (Meta-Object Compiler) which handles the C++ extensions of the Qt framework, such as signals and slots among other more handy helping features. The moc tool parses a header file containing Qt extensions and generates a C++ source file, which must be included in the compilation in order to get the desired Qt functionality working [ QtMoc ].

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

  1. The signal class
  2. The connect and the sender methods
  3. 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 F unction T ype. 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 structure slot_holder declared in the signal class. Now, this sh (slot holder, for the uninitiated) variable will be common for all the connect_i functions sharing a common prototype (hence, the static). For the perverse among you, SHT stands for S lot H older T ype; 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 the FT we discussed in the slot_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 local sh object. This is where the magic happens since this parameter is the same that is declared in the signal class, and since slot_holder<T> is a common_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






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.