DEV Community

Dave Cridland
Dave Cridland

Posted on

SigSlot

SigSlot Introduction

SigSlot is a library I adopted several years ago as a solid implemention of the "Observer Pattern" in C++.

It's available on GitHub, is free to use, and consists of a single small header file. It supports classical observer patterns with lambda and pointer-to-member connections, and also has support for use as awaitable objects in coroutines.

A little history

Originally, such things were difficult if not impossible to write in C++, and the most common implementations were either non-standard or relied on specialist preprocessors.

The essential concept is that you have a signal object:

sigslot::signal0<> my_signal;
Enter fullscreen mode Exit fullscreen mode

... which other things can listen to:

my_signal.connect(this, &MyListener::slot);
Enter fullscreen mode Exit fullscreen mode

... and the signal can then emit:

my_signal.emit(); // Calls its listeners.
Enter fullscreen mode Exit fullscreen mode

Sarah Thompson wrote the original SigSlot, having decided that the new "ISO C++" standard had sufficient flexibility in the templating language to support the concept - and indeed, it did. She was, as I understand it, explicitly trying to mirror Qt's signal and slot preprocessor.

But ISO C++ - that's C++03, in modern terms - lacked a number of useful features that would make the programmer's life smoother. Sarah's design gave signals parameters - a useful and common feature - but while she could use templates so the type of the parameters didn't matter, she had to use a different template for the different numbers of parameters.

Lambdas didn't exist either, which made for a much more natural fit.

Finally, while Sarah had made the library thread-safe, thread primitives were not covered in C++03, and so several different models needed to be provided for.

Nevertheless, when I came across the library I was pretty impressed with the simplicity - a single header file contains the entire library, and it's simple to use. So I updated things a bit - in no small part in order to learn the new (to me) C++11.

SigSlot C++11

When C++11 was widely available (in 2014), I took Sarah's SigSlot library and updated it with several features from the newer standard.

First, I used "variadic templates", which meant that there was now a single "signal" template no matter what the number or type of arguments the signal took.

Second, I gave it support for lambdas as well as the (somewhat obscure) pointer-to-member syntax.

I didn't do threading, and I later noticed other things could have been improved too. Still, it was handy, and the library shrank a lot. While the library was always a single header file, by using a bit of C++11, I shrank it from 2,500 to just over 500 lines of code.

And Now, C++17...

My last changes have been to introduce features from C++14 and C++17, as well as go over and polish some of the C++11 bits I'd missed.

The first thing to go was the different threading models - I'm now using C++11 thread primitives, and as a result, the last remaining wart in the usage has gone. So, too, did some of the templates - without the different threading models, there was no need for some of the template hierarchy (and if fact I'd missed some simplifications back in 2014).

I also added some coroutine support - this runs on both Windows using MSVC, and UNIXes using CLang. Despite adding about 100 lines of coroutine support, the library is still smaller - just 430 lines now.

Using the library hasn't really changed much since Sarah's version - indeed, this version is closer to hers than to my C++11 version in usage in some ways. In others, though, it's markedly different.

// Only one template, parameters are signal arguments:
sigslot::signal<int> my_signal;

// ...using lambdas, for example:
my_signal.connect(this, [this](int i){
  std::cout << "I was signalled with " << i << std::endl;
});

// Emitting is by either emit(...) or simply calling the signal:
my_signal(4);

// Or, do a coroutine:

coroutine_type<int> coroutine() {
  int i = co_await my_signal;
  co_return i;
}

auto c = coroutine();
// This awaits, so stops immediately.
my_signal.emit(5);
// Now it's continued and is ready to return its value:
std::cout << "I was signalled with " << c.get() << std::endl;

Enter fullscreen mode Exit fullscreen mode

There are more extensive examples in the source tree, including examples for both classical and coroutine based signals which compile clean on both UNIX and Windows.

Why?

Other signal/slot libraries exist - Boost has one, for example. So why do I persist with this one? Partly, it's the small, neat implementation - a design which predates any of my involvement.

But a small library like this is great for me to learn the newer features of C++. It's a fun playground for me, and one that yields useful code. I use SigSlot extensively in my XMPP Thing, Metre.

Feel free to play with it - or use it, even better - it has copyright disclaimed (ie, it's as close to "public domain" as it's possible to get in modern copyright law), and it's available at GitHub.

Top comments (2)

Collapse
 
mflamer profile image
Mark Flamer

Hi Dave, I would like to use your sigslots C++11 version in a project I'm working on. Looks like only the C++17 version is in your repo. Can you point me to the older, version with no threading? Thanks!

Collapse
 
mflamer profile image
Mark Flamer

I found what I was looking for in the repo history.