Home        KjellKod Code Page     


Comment: 2010-08-12

Back when I made KSignals version 1 (dynamic memory) and version 2 (static memory for embedded systems) I had not read GotW_83: Generic Callbacks  but I'm still kind of happy that I unbeknowst of much still managed to on my own come up with something very similar, although lacking some of the finer points Herb makes.


Either way 
I strongly recommend you to read Herbs Gotw article since it's very, very easy read and explains better than any other function callback or function pointer text that I've read how to setup the basics that are needed for Signals and Slots. If you think this is something good, then please go ahead and use my versions and tailor make them as you please (which should be easy since they're only few lines with bare bone code)

Cheers
Kjell Hedström



Signal and slots
is a concept developed from  Qt. It is basically a generalized implementation of the Observer pattern (see also publisher/subscriber) The purpose of the KjellKod signal-n-slot is to have the power of Observer pattern - but made  with generic function callback. The most famous implementations of Signals and Slots are made by Qt and Boost (maybe libsigc++ is worth mentioning also?)

My own implementation of signals and slots were made when I wanted to learn more about C++ generic function callbacks. Now it's a fully functional library that is in use in multiple projects.  KSignals (1 & 2) are very much simplistic signal and slot implementations, but just for that reason it is easy to use it and modify it to your own needs.

Below I've desribed two flavors of the KSignals.  The first version was the initial one with dynamic memory (slot) allocation. The second version was made for a barbone embedded system - which disallowed dynamic memory allocation on the heap. 

 A comparison between the much more advanced and feature rich Boost signals and my own KSignals (version 2) can be found at the end of this article.

 Example of Signals (other than KSignals can be found in this  article by Scott Collins)

My own signal-n-slot definition 

The KjellKod signal-slot mechanism is a C++, cross platform compatible implementation of the  Observer design pattern. 

Signals are basically notifications of an (observable) event. Signals are connected to Slots which each store a function callback to an object.  A signal can be connected to many slots and all slots/receivers are notified when the signal is emitted.

A signal can be just a notification, or it can pass along information. This make's it very handy when creating loosely coupled software systems. Normally such systems are made with a Publisher-Subscriber pattern (a.k.a. Observer) but with signals you have less virtual call overhead and you don't have to implement a given interface to have the power of loose coupling.

 

The only requirement on an objects function callback that is to be stored within a slot is that it must be able to receive the same argument(s) as the publishing signal is sending.

 I.e. If it is a void signal, then the slot (stored callback function) must have a zero argument list. Likewise, if the signal sends out an argument, the receiving function must have that argument type, and only that argument in its argument list.  If this requirement is not adhered to, the compiler will generate an error message. I.e. signal/slot is typesafe.

Let show this with some examples. Since the syntax of my two flavors of KSignals is different I show both versions.

Note: All code below written with formatting to be easy to read on a webpage, not necessarily good 'c++ coding style' formatting.


Lets try it with an example

KSignal flavor with no dynamic memory allocation necessary. One benefit of this way of doing it is that the slot function clearly is shown in the signal receiving object,. making it easier to follow in code which functions may be recipients of signal calling. To distinguish between the KSignal flavors I use in this example the namespace  Rt  for lack of better name (anyone with a suggestion?)

struct HelloWorld
{
 HelloWorld()
:
slotVoid(this, &HelloWorld::hello) // passing this in initialization list gives a warning but , slotStr(this, &HelloWorld::hello) // it's safe. It won't be used till at least it's fully created { /* ... */ } /** Function with 'void' argument. Below is a slot declared for a signal<void> i.e. signal0 */ void hello() { std::cout << "Hello World" << std::endl; } Rt::Slot0<HelloWorld> slotVoid;
  /** Function with 'std::string' argument. Below is a slot declared for a signal<std::string> i.e. signal1 */
void
hello(std::string msg) { std::cout << "Hello " << msg.c_str() << std::endl; }
Rt
::Slot1<std::string, HelloWorld> slotStr;

};
int main( int argc, const char* argv[] )
{
HelloWorld elMundo; Rt::KSignal0<> sig0; // void and std::string passing signals
Rt::KSignal1<std::string> sig1;
sig0.connect(&elMundo.slotVoid); // signal to slot connections
sig1.connect(&elMundo.slotStr);
// sig1.connect(&elMundo.slotVoid); gives compilation error - signal slot connections are typesafe

sig1.emit("Mustafa");
sig0.emit();  
    return 0;
}
 

// output 
// Hello Mustafa
// Hello World

 

Let's try it again but this time with the original KSignal flavor


Using dynamic memory allocation (slots) and no explicit declaration of slots in the source code. Another difference is that the functions cannot be named identically,. this version of the KSignals cannot work out the template arguments for the ambiguous calls.

struct HelloWorld
{
void hello1() const { std::cout << "Hello World" << std::endl; }
void hello2(std::string msg) const { std::cout << "Hello " << msg.c_str() << std::endl;}
};

int main( int argc, const char* argv[] )
{

HelloWorld elMundo;
ksignal0::Signal0 sig0; // void and std::string passing signals
ksignal1::Signal1<std::string> sig1;

ksignal0::connect(sig0, elMundo, &HelloWorld::hello1); // signal to slot connections.  connection takes 3 arguments:
    ksignal1::connect(sig1, elMundo, &HelloWorld::hello2); //signal, the object, a function pointer (callback) to the object 
 
    sig1.emit("Mustafa");
sig0.emit();  
    return 0; 
}


What more


Yes - I can also mention that both flavors of KSignal detects recursive calling of the signals and asserts if that happens. This is something that COULD be disallowed,. but in my experience it is usually by mistake such behaviors and thereby by default not allowed. 

Example: 
struct SomeClass
{
 SomeClass()
:
slot(this, &SomeClass::func)
{}
void func(){ sig.emit(); }
Rt
::Slot0<SomeClass> slot; 
 
 Rt::KSignal0<> sig;
};
int main( int argc, const char* argv[] )
{

SomeClass s1;
SomeClass s2;

s1.sig.connect(&s2.slot);
s2.sig.connect(&s1.slot);
s1.sig.emit();
return 0;
}

// output 
// Assertion failed: "abort due to recursive calling of emit"

Yet another improvement in the case of not allowing recursive calling and finding out exactly what the culprit is could be to use macros with #name and __FILE__ and __LINE__ when declaring the signals. I've not attached the code for how this is done but e-mail me and I'll post it or mail it if it is of interest

Example: in the above example the struct SomeClass would use the macro
/* When declaring a signal wrapped in SIG_DECLARE, it uses a a macro that puts 
#signame, __FILE__, __LINE__ input to the constructor,
making it easier to find fatal errors like
recursive calling. This is normally done in class/struct initialization list */
#ifndef SIG_DECLARE
#define SIG_DECLARE(sig) sig(#sig, __FILE__, __LINE__)
#endif

I.e. the syntax would be 

struct SomeClass
{
 SomeClass()
:
slot(this, &SomeClass::func)
 , SIG_DECLARE(sig)
{}
void func() { sig.emit(); } Rt::Slot0<SomeClass> slot; Rt::KSignal0<> sig;
};

// output, somwwhat in the style of 
// Assertion failed: "Abort due to recursive calling"
 "SomeClass::sig declared at file: SomeClass.h, line: 25"


Future version of KSignals are already thought of, but with a recent addition to the family :-) it's on hold for now ... 

Anyhow, in the next version I had planned to use with dynamic number of slots, but using syntax of explicit declaration of the slots. Preferably as they are now (up to the programmer) i.e. explicitly declared next to the recipient function.

Another improvement would be to remove the Signal0 and Signal1<Type> syntax difference. It would be better to always use the syntax of Signal<Type> where Type could be either void or a non-void type argument.


Further using the same concept as Boost in the area of shared_ptr and weak_ptr and their inner workings of shared_cntr and weak_cntr it is feasible to have auto disconnect on when objects go out of scope. ... However this would move KSignal towards more feature rich areas and flirting with the idea of thread safety.

... and I have not planned to make the next KSignal version threadsafe. Well it all depends what you put into the word thread-safe. But for communication between threads I would much rather then use the concept of Active objects (I hope to write more about that later) queues and maybe futures. Using signals of any type (boost, sigslot, libsig or ksignal)  to communicate between threads must be done VERY carefully but preferably not at all since it essentially will call 'unknown code' from the thread space perspective at least.



Pros and Cons


Cons

  • Having too many signal - slot calls instead of normal function calls would made the code incomprehensible. Using the explicit slot declaration somewhat rectifies this.
  • KSignals are not threadsafe in that they do not have atomic disconnect/connect. They are not reentrant since a signal member variable is used to verify whether recursive calling is used  (recursive calling is considered an error). Changing it to making signals reentrant and allowing recursive calling is an small and easy change.  
  • No auto-disconnect if an object stored in a slot is deleted. Emitting a signal to a slot in a destroyed object is obviously an error (crash). Fixing this (a la boost) requires some work but is definitely doable.
  • Will take more time/space than a direct function call but less time than a virtual function call. Which would be the case if you instead used the Publisher-Subscriber (a.k.a. Observer) solution.

Pros

  • Easy to understand syntax and usage. A signal is an object and you call emit just like you call another function. It is also type safe and argument list safe.
  • It is a very valuable tool when putting together software pieces that doesn't or shouldn't be closely coupled.
  • It is a definite improvement of the Observer design pattern - and should in my opinion be used instead.
  • KSignals doesn't require a lot of libraries to install, it's just a few files and with a license that put basically no limits on the use of the software whether or not commercial or open source. 
  • KSignals is free, open source, do with it what you like - and the code is so bare boned and simple that it should be easy to modify it to your specific needs or to study it for understanding of signals and generic function callback.
  • Slot functions can be inlined (I put this note here since I read in Scott Collins article that wasn't the case for all signal/slot libraries)

Lets make a Boost and KSignal comparison. Differences are marked.



I haven't explained below why I believe some things are better than other but if I get flamed I might to this :-)

Boost.Signals is a beautiful made library. But the ambiguous way in which it can be used and that it copies the object with the slot receiving function is in my opinion a bad design decision (correct me if I'm wrong about the copying part)

In contrast, KSignals, by explicitly declaring the slot in the class with the slot-function it is abundantly clear for the developer (future maintenance etc) that a signal may connect to a specific function.

In my own biased opinion I would say that Simple is Better   :)


Boost.Signals KSignals
a signal is an object a signal is an object
a signal is emitted by calling it like a function. I.e. Slot function is always the operator ( ) 
a signal is emitted by calling with the emit keyword.No restriction on function name as long as argument list matches.
signals can be global, local, or member objects signals can be global, local, or member objects
any code with sufficient access to connect to the signal can also cause it to be emitted any code with sufficient access to connect to the signal can also cause it to be emitted
a slot is any callable function or function-object a slot is any callable function within an object
can return values collected from multiple slots
no return
synchronous synchronous
not thread-safe not thread-safe
Pro: It is possible to have auto-disconnect on slot destruction : if and only if the slot is trackable

Con: I.e. there are several ways to define signals - this can be confusing and give more error prone usage of the library
Currently not any released version with auto-disconnect on slot destruction.

type-safe (compile-time checked).  argument-list much match exactly
type-safe (compile-time checked).  argument-list must match exactly
allows recursive calls
disallows recursive calls
signals, slots may be templates signals, slots may be templates
implemented via straight C++ implemented via straight C++
Copies the object that keeps the slot function.  Does not copy the object that keeps the slot function. 



Conclusions


Signals and slots are definitely an improvement of the Observer Pattern. It is a great tool for making software with separate components, it promotes loosely coupled design. Signals exist with many flavors of which this text describes a few.

Using KSignals you have a statically type-safe, template-based, bare bone and easy to modify implementation of signals and slots. Signals are used just as any other object with functions. Slots (in the case of Rt::slot) are explicitly declared in the receiving object - making it obvious what functions may be recipient of a signal call.

Compared to the Observer pattern that use virtual calls the KSignal implementation is faster since there is no virtual call overhead (For details I can on request give information about this: I measured on a 300Mhz PPC, bare boned embedded system for comparison reasons). 

Signals (no matter what type) should be used in moderation like everything else. But in the right place they promote good loosely coupled software that is easy to understand, flexible, re-usable and easy to test.


Comments?


I appreciate feedback. You can reach me through my blog. This entry can be commented on 
http://kjellkod.blogspot.com/2007/10/ksignal-signal-and-slot-observer-design.html

The KSignal Code Download

updated 2009-09-25
1) VC++: Code + publisher_subscriber example (zip)

2) Rt ksignals made for a bare bone embedded system with no dynamic memory allocation. Using explicit slot declaration.
Also with examples  (zip)


2007
C++ code (zip)
VC++ project & code (zip)


VC++ KSignal/Elevator 

example (
zip)



KjellKod coding License