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 :
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: sig1.run() ; 23: 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: sig2.run() ; 31: 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... ::f() ::C[0xbffff7c0]::m1()
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... ::f() ::f() ::C[0xbffff7c0]::m1() ::C[0xbffff7c0]::m1()
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 :
bool ensure_unique() const
returns the state of the
uniqueness : true
if new connections will ensure
uniqueness, false
otherwise ;
SlotSigErrors set_ensure_unique(bool)
allow to set the
state of uniqueness ; the new state will be used for new
connections, existing ones won't be changed : if you enable
uniqueness on a signal for which it was disabled, duplicate connections
won't be removed by this method ; but keep reading...
SlotSigErrors enforce_unique()
will check existing
connections in this signal and remove the duplicated ones ; you can
call it whichever is the state of uniqueness, and it won't change it.
Please note this last method can be rather long, especially if many slots are connected, so don't overuse it.
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
generate_slotsig.py
, 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 generate_slotsig.py 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.
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
.
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.
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 ; sig.run() ;
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.
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 :
in_class_void() const
returns a void*
pointer, which has the same value as the pointer given to the signal's
connect()
method used to connect a method in a class
instance ; it will always be null for global functions ;in_base() const
returns a
SlotSig::SlotsSetBase*
pointer, which will be non-null only
if the class holding the slots inherits from
SlotSig::SlotsSetBase
(it will be null for global
functions).For now, it's not quite easy to do something really interesting with slots classes. This will probably change in the future.
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