SlotSig Advanced documentation

This page gives some informations about advanced aspects of the SlotSig library. If you haven't already done so, you're advised to first read the tutorial before this. You might also be interested in some benchmarks done on SlotSig and others.

Content :

About the uniqueness of a connection

It has been said in the tutorial that if you connect twice (or more) a given signal to the same slot, the slot won't be called twice (or more) : this is what I call the uniqueness of a connection, and it's the default way of connecting of SlotSig. However it has a drawback : it makes connecting a slot to a signal much slower, which may be a concern if you plan to connect many slots to a signal. You may also want to call a slot as many time as it has been connected.

This is in fact possible : each slot class's constructor takes an optional boolean parameter. It defaults to true, but if you give false, than you can connect a slot several time to the same signal.

For example :

01: #include <iostream>
02: #include <slotsig/slotsig_0.h>
03: void f()
04: { std::cout << "::f()\n" ; }
05: class C : public SlotSig::SlotsSetBase
06: {
07:   public:
08:     C() : SlotSig::SlotsSetBase()
09:     { std::cout << "::C instance at " << this << "\n" ; }
10:     void m()
11:     { std::cout << "::C[" << this << "]::m1()\n" ; }
12: } ;
13: int main (int argc, char* argv[])
14: {
15:   SlotSig::Sig0<void> sig1 ;
16:   C c ;
17:   std::cout << "Connecting to sig1...\n" ;
18:   sig1.connect(f) ;
19:   sig1.connect(f) ;
20:   sig1.connect(&c, &C::m) ;
21:   sig1.connect(&c, &C::m) ;
22: ;
24:   std::cout << "Connecting to sig2...\n" ;
25:   SlotSig::Sig0<void> sig2(false) ;
26:   sig2.connect(f) ;
27:   sig2.connect(f) ;
28:   sig2.connect(&c, &C::m) ;
29:   sig2.connect(&c, &C::m) ;
30: ;
32:   return 0 ;
33: }

(code here)

Just defined a global function and a class containing a single method. Two signals are declared, lines 15 and 25. The first one, sig1, uses default parameters. So when slots are connected twice (lines 18-19 and 20-21), they will be called only once when you run the program :

::C instance at 0xbffff7c0
Connecting to sig1...

The second one, sig2, is given false to the constructor : nothing is done to make sure a slot isn't already connected. So slots connected twice will be called twice :

Connecting to sig2...

Keep in mind that in the case of a class's method, what defines a slot is both the pointer-to-method (second parameter given to connect()) and the instance's address (first parameter given to connect()). So if you declare another instance of C, and connect the method m() to sig1 using this instance, then m() will be called twice : the first one in the context of the first instance of C, the second one in the context of the second instance of C.

There are three methods in signals classes related to this feature :

Please note this last method can be rather long, especially if many slots are connected, so don't overuse it.

Expanding or tweaking SlotSig

By default, SlotSig comes with the support for up to 16 parameters. If this is not anough for you, you can expand this limit with the Python script, located in the root directory of SlotSig's distribution. It takes a single parameter, the maximum number of parameters you want. For example, if you want up to 24 parameters, simply run :

$ python 24

This will create the files slotsig_n.h in the slotsig directory, with n between 0 and 24 (included), as well as corresponding test programs in the tests directory.

This script relies on two files, slotsig_template.h and test_template.cpp. The first is parsed to produce the actual library headers files. If you want or need to change a little bit the code somewhere, do it in the template file : this will allow you to easily spread your changes in all header files.

Back on sending parameters

About sending parameters... SlotSig tries to “guess” the best way to transmit parameters, to avoid useless copying. However you're advised to pass references when possible, moreover if you transmit large structures.

When dealing with references, you have to take care of the type you give to the signal and the slot : they must match precisely. For example, this code won't compile for at two reasons :

01: #include <iostream>
02: #include <slotsig/slotsig_1.h>
03: void f1(int a)
04: {  }
05: void f2(int& a)
06: { }
07: int main (int argc, char* argv[])
08: {
09:   SlotSig::Sig1<void, int> sig1 ;
10:   SlotSig::Sig1<void, int&> sig2 ;
11:   sig1.connect(f2) ;  // wrong !
12:   sig2.connect(f1) ;  // wrong !
13:   return 0 ;
14: }

(code here)

Lines 11 and 12 are errors.

sig1 is declared as a signal sending one parameter, of type int. But line 11 tries to connect it to a function taking an int& : parameters types are not the same, the compiler shall refuse this. Line 12 is a simple mirror, the signal sig2 sends a parameter of type int&, and we try to connect it to a function taking a simple int.

Cloning signals

Since release 0.6, signals can be cloned, affected or copied. The basic way to achieve this is to use the clone() method, which will return a signal of the same type. For example :

SlotSig::Sig2<int, double, char*> sig_1 ;
/* connect things to sig_1... */
SlotSig::Sig2<int, double, char*> * sig_2 = sig_1.clone() ;
SlotSig::Sig2<int, double, char*> sig_3(sig_1) ; // copy constructor
SlotSig::Sig2<int, double, char*> sig_4 ;
/* connect things to sig_4... */
sig_4 = sig_1 ;

After this, sig_2 and sig_3 are connected to the same slots than sig_1. As long as you don't connect or disconnect anything to/from one of these three signals, running one is the same as running another. There might be a small difference though : the slots (or chained signals) might not be called in the same order.

Now if we consider sig_4, to which some slots (or signals) have been connected. After the last line, sig_4 contains also the same slots as sig_1, and no more the ones it had before. The last line is equivalent to disconnect everything from sig_4 (from example, using the disconnect_all() method), then connect the same things that are in sig_1.

The uniqueness, enabled or not, is preserved in the cloned signal. Also automatic disconnections when needed (e.g. when destroying the cloned signal, or a class containing slots) are correctly handled - or at least, they should be, if it's not the case mail me.

Connecting methods in unmanaged classes

Until now, it has been stated that to connect a method in a class to a signal, this class should inherit from SlotSig::SlotsSetBase. This is highly recommended, if you want to benefit of automatic disconnections upon deletion of this class's instances, but it's not a real must. You're free to connect any method in any class, the only real must is that this class must contain at least one virtual method (even a virtual, empty destructor will do).

However, in this case you're on your own to disconnect what should no longer be called. However, SlotSig provides a helper function : about_to_delete(). You should call it when you're about to delete an instance which contains slots (connected methods).

Thus a safe code would look like this :

class MyClass
{ public:
    void f() { }
/* ... */
MyClass * instance = new MyClass ;
SlotSig::Sig0<void> sig ;
sig.connect(instance, &MyClass::f) ;
/* ... */
SlotSig::about_to_delete(instance) ;
delete instance ; ;

Without the call to about_to_delete(), the last line would most probably crash. This magic function will make sure all involved signals are disconnected.

Parsing slots with iterators

Since release 0.6, it is now possible to parse the slots connected to a signal. Have a look at this example program :

01: #include <iostream>
02: #include <slotsig/slotsig_0.h>
03: void f()
04: { std::cout << "::f()\n" ; }
05: class SlotSet : public SlotSig::SlotsSetBase
06: {
07:   public:
08:     void m() { std::cout << "SlotSet::m()\n" ; }
09:     void m2() { std::cout << "SlotSet::m2()\n" ; }
10: } ;
11: class OtherClass
12: {
13:   public:
14:     virtual ~OtherClass() { }
15:     void m() { std::cout << "OtherClass::m()\n" ; }
16:     void m2() { std::cout << "OtherClass::m2()\n" ; }
17: } ;
18: int main (int argc, char* argv[])
19: {
20:   SlotSig::Sig0<void> sig ;
21:   SlotSet slot_set ;
22:   OtherClass other_class ;
23:   sig.connect(f) ;
24:   sig.connect(&slot_set, &SlotSet::m) ;
25:   sig.connect(&slot_set, &SlotSet::m2) ;
26:   sig.connect(&other_class, &OtherClass::m) ;
27:   sig.connect(&other_class, &OtherClass::m2) ;
28:   SlotSig::Sig0<void>::const_iterator iter = sig.begin() ;
29:   while ( iter != sig.end() )
30:   {
31:     SlotSig::Slot0<void>* slot = *iter ;
32:     if ( slot != 0 )
33:     {
34:       void* in_class = slot->in_class_void() ;
35:       std::cout << "Slot, in class " << in_class ;
36:       if ( in_class != 0 )
37:       {
38:         if ( in_class == &slot_set )
39:           std::cout << " SlotSet" ;
40:         else
41:           if ( in_class == &other_class )
42:             std::cout << " OtherClass" ;
43:           else
44:             std::cout << " <unknown>" ;
45:         SlotSig::SlotsSetBase * in_base = slot->in_base() ;
46:         if ( in_base == 0 )
47:           std::cout << " (unmanaged)\n" ;
48:         else
49:           std::cout << " (SlotsSetBase-based)\n" ;
50:       }
51:       else
52:       {
53:         std::cout << " ==> it's a global function, " ;
54:         SlotSig::SlotGlobal0<void>* slot_global =
55:           dynamic_cast<SlotSig::SlotGlobal0<void>*>(slot) ;
56:         std::cout << std::hex << (void*)(slot_global->the_slot()) << "\n" ;
57:       }
58:     }
59:     ++iter ;
60:   }
61:   SlotSig::about_to_delete(&other_class) ;
62:   return 0 ;
63: }

(code here)

A single signal is connected to five slots, one global and four in two class, among which one does not inherit from SlotSig::SlotsSetBase.

Each signal class provides iterators type (iterator and const_iterator), as well as begin() and end() methods (const and non-const), much like standard containers. Dereferencing a valid iterator will give you an instance of a slot class, SlotSig::Slotn<args>, where n is the number of parameters, and args are the same template parameters given to the corresponding signal class (see line 31). These slot classes offers some methods :

For now, it's not quite easy to do something really interesting with slots classes. This will probably change in the future.

Error codes

The methods connect(), disconnect(), run(), set_ensure_unique(), enforce_unique() and run_if() return an error code (new in release 0.4). It's a value from the enum SlotSig::SlotSigErrors, explained here :

Code Details Returned by


No problem All
NullGlobalFct Given a null global function connect(), disconnect()
KnownGlobalFct The given global function is already connected to this signal connect()
UnknownGlobalFct The given global function is not connected to this signal disconnect()
DynCastFailed Internal error, mainly caused by a non-virtual class when trying to connect a method in a class connect()
NullInstance Given a null pointer to some class's instance connect(), disconnect()
KnownSlot The given method in the given instance is already connected to this signal connect()
UnknownSlot The given method in the given instance is not connected to this signal disconnect()
IsEmitting The signal is currently calling slots, you can't do anything All
EmitInterrupted The signal has been interrupted by a predicate run_if()
NullSignal Given a null pointer for a signal to chain connect(), disconnect()
KnownSignal The given signal is already chained from self connect()
UnknownSignal The given signal is not chained from self disconnect()
UnknownClass The class is unknown, it contains no slot connected to self disconnect_all()

You can check if a signal is currently calling slots with the method is_emitting(), which returns a boolean value.

These values can be converted to a printable string (const char*) using the global function :

const char* SlotSig::error2str(SlotSig::SlotSigErrors code) ;

Last update 2004-06-13