Difference between revisions of "Using libsigc++ signals"
m (→Memory safety: Fix Emitter -> Receiver) |
|||
(5 intermediate revisions by 2 users not shown) | |||
Line 4: | Line 4: | ||
Signals are type-safe callbacks, essentially lists of function pointers on steroids. Each signal stores a list of '''slots'''. A slot defines what should be done when the signal is '''emitted'''. Typically an object emits the signal when it changes state. When a signal is emitted, the slots from the signal's slot list are executed, and sometimes a result is returned. | Signals are type-safe callbacks, essentially lists of function pointers on steroids. Each signal stores a list of '''slots'''. A slot defines what should be done when the signal is '''emitted'''. Typically an object emits the signal when it changes state. When a signal is emitted, the slots from the signal's slot list are executed, and sometimes a result is returned. | ||
Note that libsigc++ signals have absolutely nothing to do with POSIX signals. | Note that libsigc++ signals have absolutely nothing to do with POSIX signals. They are however almost identical to Qt signals and slots. | ||
==Defining signals== | ==Defining signals== | ||
Signals are typically defined as public attributes like this: | Signals are typically defined as public attributes like this: | ||
class Emitter { | class Emitter | ||
{ | |||
public: | public: | ||
void increaseNumber(int by) { | void increaseNumber(int by) { | ||
_number += by; | _number += by; | ||
signal_number_changed.emit(by); | signal_number_changed.emit(by); | ||
} | } | ||
int getNumber() { return _number; } | |||
int getNumber() const { return _number; } | |||
'''sigc::signal<void> signal_number_changed;''' | |||
'''sigc::signal<void (int)> signal_number_changed;''' | |||
private: | private: | ||
int _number | int _number = 0; | ||
}; | }; | ||
std::ostream &operator<<(std::ostream &o, Emitter &e) { | |||
std::ostream &operator<<(std::ostream &o, Emitter const &e) { | |||
o << e.getNumber(); | o << e.getNumber(); | ||
return o; | return o; | ||
Line 30: | Line 32: | ||
Several Inkscape classes use private signal attributes and define accessor methods for connecting slots, but this prevents you from using some advanced features. Wrapping them all in accessors isn't worth the hassle. | Several Inkscape classes use private signal attributes and define accessor methods for connecting slots, but this prevents you from using some advanced features. Wrapping them all in accessors isn't worth the hassle. | ||
<tt>signal</tt> is a class template. | <tt>signal</tt> is a class template. It takes a function type parameter defining the types of parameters passed to each slot when the signal is ''emitted'', and the type that the slot must return. | ||
==Connecting to signals== | ==Connecting to signals== | ||
The <tt>signal</tt> class has a <tt>connect</tt> method, which takes a slot and adds it to the list of the signal's slots. | The <tt>signal</tt> class has a <tt>connect</tt> method, which takes a slot and adds it to the list of the signal's slots. Any '''function object''' can be used as a slot, including any lambda. Raw function pointers and member function pointers can also be used, provided they are wrapped as function objects using <tt>sigc::ptr_fun()</tt> and <tt>sigc::mem_fun()</tt> respectively. Here is a typical example: | ||
class Receiver { | class Receiver | ||
{ | |||
public: | public: | ||
Receiver(Emitter &emitter) { | Receiver(Emitter &emitter) { | ||
Line 42: | Line 45: | ||
); | ); | ||
} | } | ||
private: | private: | ||
void _handleNumberChange(int | void _handleNumberChange(int increment) { | ||
std::cout << "The number increased by " << | std::cout << "The number increased by " << increment << std::endl; | ||
} | } | ||
}; | }; | ||
<tt>sigc::mem_fun</tt> | <tt>sigc::mem_fun()</tt> produces a function object that invokes a given method on the given object when it is called. Now whenever the number stored in the <tt>Emitter</tt> class changes, all <tt>Receiver</tt> objects created with this <tt>Emitter</tt> object will have the <tt>_handleNumberChange()</tt> method called. | ||
==Memory safety== | |||
The above example is only memory-safe if the lifetime of the <tt>Receiver</tt> can be guaranteed to end before the lifetime of the <tt>Emitter</tt>. Otherwise, there is nothing stopping the slot being invoked on a destructed <tt>Receiver</tt>. If this happens in our trivial example it will be harmless, but most of the time it will be a use-after-free, and a crash. | |||
If you are used to Qt signals and slots, this will come as a surprise, because in Qt slots are automatically disconnected when objects are destroyed, making them memory-safe by default. There is no such guarantee in libsigc++; safety must instead be opted-into. | |||
There are two ways to solve this problem. | |||
===Automatic disconnection=== | |||
Make <tt>Receiver</tt> publically derive from <tt>sigc::trackable</tt>. Then use any of <tt>sigc::mem_fn()</tt>, <tt>sigc::track_obj()</tt> or <tt>sigc::track_object()</tt> to create slots. The slot will then be disconnected when the object is destroyed. | |||
Here are some further comments and caveats with this approach: | |||
*If you forget to make make <tt>Receiver</tt> inherit from <tt>sigc::trackable</tt>, then <tt>sigc::mem_fn()</tt> and <tt>sigc::track_obj()</tt> will silently fall back to being unsafe, which makes code that uses them fragile. By contrast, <tt>sigc::track_object()</tt> will refuse to compile. However, at the time of writing, the superior <tt>sigc::track_object()</tt> cannot be used yet due to not being supported on all build targets. | |||
*Even with the above approach, signals will be disconnected during the destructor of the <tt>sigc::trackable</tt> base subobject, which happens ''after'' the destructor of the <tt>Receiver</tt> subobject. So it is entirely possible for a sufficiently complicated class hierarchy, written by many different authors and performing complex signal manipulations in its destructor, to invoke slots on destructed objects even when the best practices for <tt>sigc::trackable</tt> and <tt>sigc::track_obj()</tt> are followed. This is not an issue for simple classes, or for the approach of the next section. | |||
===Manual disconnection === | |||
The <tt>connect()</tt> function actually returns a <tt>sigc::connection</tt> object with a <tt>disconnect()</tt> method that can be used to disconnect the connection. We can use this to improve our example: | |||
class Receiver | |||
{ | |||
public: | |||
Receiver(Emitter &emitter) { | |||
'''_conn =''' emitter.signal_number_changed.connect( | |||
sigc::mem_fun(*this, &Receiver::_handleNumberChange) | |||
); | |||
} | |||
'''~Receiver() { _conn.disconnect(); }''' | |||
private: | |||
'''sigc::connection _conn;''' | |||
void _handleNumberChange(int increment) { | |||
std::cout << "The number increased by " << increment << std::endl; | |||
} | |||
}; | |||
However, when working with <tt>sigc::connection</tt> there are still a number of traps that make it extremely easy to write bugs: | |||
*If you destroy a connection without disconnecting it, it will not automatically be disconnected. | |||
*Similarly, if you overwrite a connection with another one, the original will not automatically be disconnected. | |||
These errors are some of the most frequent sources of use-after-frees in Inkscape. The problem is that <tt>sigc::connection</tt> behaves a lot like a raw pointer, with <tt>disconnect()</tt> being another way of spelling delete. What is needed is something that behaves more like a <tt>std::unique_ptr</tt>. For that reason, Inkscape has a RAII wrapper around <tt>sigc::connection</tt> called <tt>Inkscape::auto_connection</tt> that does not have these issues, and should be preferred wherever possible. | |||
==Adaptors== | ==Adaptors== | ||
As you can see, we cannot directly access the object that emitted the signal from the handling method. You might think that changing the signal definition in the Emitter class to | As you can see, we cannot directly access the object that emitted the signal from the handling method. You might think that changing the signal definition in the Emitter class to | ||
sigc::signal<void | sigc::signal<void (Emitter &, int)> signal_number_changed; | ||
is the way to go. However, there are a few problems: first, the Emitter parameter for this slot is always the same (when you connect to another Emitter object from the same Receiver, you are creating a different slot stored in a different Emitter object). Moreover, it may be possible that some handlers only need the information about the number change - for instance, when you derive a class from Emitter and connect to the signals of the superclass, you already | is the way to go. However, there are a few problems: first, the Emitter parameter for this slot is always the same (when you connect to another Emitter object from the same Receiver, you are creating a different slot stored in a different Emitter object). Moreover, it may be possible that some handlers only need the information about the number change - for instance, when you derive a class from Emitter and connect to the signals of the superclass, you already have the pointer to the superclass instance in <tt>this</tt>. There is a much better way to solve this problem. Another type of slots that libsigc++ provides are called '''adaptors''' - they allow you to change the type signature of your handler function by binding constant parameters to its invocation, ignoring some parameters supplied by the signal, and more. | ||
===Binding parameters=== | ===Binding parameters=== | ||
Line 99: | Line 148: | ||
Note that there is an inversion of meaning: you use <tt>sigc::bind</tt> to supply something that isn't present in the signal, and <tt>sigc::hide</tt> to ignore something that isn't needed by the slot. With return adaptors, it's the other way around: you use <tt>sigc::bind_return</tt> to supply something that isn't present in the slot, and <tt>sigc::hide_return</tt> to ignore something that isn't needed by the signal. | Note that there is an inversion of meaning: you use <tt>sigc::bind</tt> to supply something that isn't present in the signal, and <tt>sigc::hide</tt> to ignore something that isn't needed by the slot. With return adaptors, it's the other way around: you use <tt>sigc::bind_return</tt> to supply something that isn't present in the slot, and <tt>sigc::hide_return</tt> to ignore something that isn't needed by the signal. | ||
===Advanced: reordering parameters=== | === Advanced: reordering parameters=== | ||
Another powerful type of adaptor is <tt>sigc::group</tt>. It allows you to execute arbitrary mathematical expressions on the parameter list before pasing it to the handler. Here is an example. | Another powerful type of adaptor is <tt>sigc::group</tt>. It allows you to execute arbitrary mathematical expressions on the parameter list before pasing it to the handler. Here is an example. | ||
Line 132: | Line 181: | ||
double length_from_last_slot = signal_get_length.emit(); | double length_from_last_slot = signal_get_length.emit(); | ||
This | This is not ideal, because you lose data from other slots. libsigc++ provides a powerful mechanism for handling return values from multiple slots, called accumulators. | ||
===Accumulators=== | ===Accumulators=== | ||
Line 148: | Line 197: | ||
}; | }; | ||
There a few important things to notice: | There a few important things to notice: | ||
# Each accumulator must have two public members: a type called <tt>result_type</tt> which defines the return value of the signal's <tt>emit()</tt> function, and an overloaded operator(), taking two arguments of some type (the type in question is called <tt>sigc::internal::slot_iterator_buf</tt>, but it's better to just use a template). | #Each accumulator must have two public members: a type called <tt>result_type</tt> which defines the return value of the signal's <tt>emit()</tt> function, and an overloaded operator(), taking two arguments of some type (the type in question is called <tt>sigc::internal::slot_iterator_buf</tt>, but it's better to just use a template). | ||
# The parameters of the function call operator are two iterators, pointing to the start and the end of the slot list. The expression <tt>*i</tt> evaluates the slot (i.e. calls it and returns the result). | #The parameters of the function call operator are two iterators, pointing to the start and the end of the slot list. The expression <tt>*i</tt> evaluates the slot (i.e. calls it and returns the result). | ||
# It is safe to write things like <tt>sum += (*i) * (*i);</tt> because the results are buffered - each slot is evaluated at most once in each signal emission. | #It is safe to write things like <tt>sum += (*i) * (*i);</tt> because the results are buffered - each slot is evaluated at most once in each signal emission. | ||
To use the accumulator, you need to changed the signal definition a bit. | To use the accumulator, you need to changed the signal definition a bit. | ||
Line 161: | Line 210: | ||
WARNING: There is an old bug in libsigc++ that makes accumulators severely broken, because their result type must be implicitly convertible to and from the slot return type. See [http://bugzilla.gnome.org/show_bug.cgi?id=586436 this GNOME bug.] It will be fixed in the next libsigc++ release. --[[User:Tweenk|Tweenk]] 09:27, 31 December 2009 (UTC) | WARNING: There is an old bug in libsigc++ that makes accumulators severely broken, because their result type must be implicitly convertible to and from the slot return type. See [http://bugzilla.gnome.org/show_bug.cgi?id=586436 this GNOME bug.] It will be fixed in the next libsigc++ release. --[[User:Tweenk|Tweenk]] 09:27, 31 December 2009 (UTC) | ||
==Conventions and tips== | ==Conventions and tips== | ||
* Your signals should only have parameters that can change with each invocation. For example, a position change signal could have the old and new positions as the parameters. In particular, signals | *Your signals should only have parameters that can change with each invocation. For example, a position change signal could have the old and new positions as the parameters. In particular, signals should not have the emitting object as a parameter. The proper way to connect to a signal from a different object is to use <tt>sigc::bind</tt> like this: | ||
class SomeObject { | class SomeObject { | ||
public: | public: | ||
Line 177: | Line 226: | ||
sigc::mem_fun(other_object, &OtherObject::onUpdate), | sigc::mem_fun(other_object, &OtherObject::onUpdate), | ||
some_object)); | some_object)); | ||
In the example, a reference is bound (because the SomeObject parameter will never need to be NULL), but you can also use a pointer. | In the example, a reference is bound (because the SomeObject parameter will never need to be NULL), but you can also use a pointer. | ||
* Use the C++ wrappers for GTK+ as a reference for good signal parameter choices. | *Use the C++ wrappers for GTK+ as a reference for good signal parameter choices. | ||
* The memory and performance overhead of adding signals to an object is quite small - most things are handled at compile time thanks to the use of templates. | *The memory and performance overhead of adding signals to an object is quite small - most things are handled at compile time thanks to the use of templates. | ||
* When using [[Boost shared pointers]], you cannot bind them as the "emitting object" parameter to slots. That's because the shared pointer will be copied into the object's slot list, keeping it in memory indefinitely. You need to use a weak pointer, which won't prevent the object's desctruction, but can be used to obtain a regular shared pointer. See [[Shared pointers]] for a more detailed guide. | *When using [[Boost shared pointers]], you cannot bind them as the "emitting object" parameter to slots. That's because the shared pointer will be copied into the object's slot list, keeping it in memory indefinitely. You need to use a weak pointer, which won't prevent the object's desctruction, but can be used to obtain a regular shared pointer. See [[Shared pointers]] for a more detailed guide. | ||
==See Also== | ==See Also== | ||
[http://libsigc.sourceforge.net/libsigc2/docs/index.html libsigc++ reference documentation] | [http://libsigc.sourceforge.net/libsigc2/docs/index.html libsigc++ reference documentation] | ||
[http://bugzilla.gnome.org/show_bug.cgi?id=586436 Gnome bug #586436 - epic accumulator fail] | [http://bugzilla.gnome.org/show_bug.cgi?id=586436 Gnome bug #586436 - epic accumulator fail] |
Latest revision as of 12:24, 4 February 2023
This is a very short guide to using libsigc++ signals. They can make complex code with a lot of things to update after each change in some data structure very simple.
What are signals?
Signals are type-safe callbacks, essentially lists of function pointers on steroids. Each signal stores a list of slots. A slot defines what should be done when the signal is emitted. Typically an object emits the signal when it changes state. When a signal is emitted, the slots from the signal's slot list are executed, and sometimes a result is returned.
Note that libsigc++ signals have absolutely nothing to do with POSIX signals. They are however almost identical to Qt signals and slots.
Defining signals
Signals are typically defined as public attributes like this:
class Emitter { public: void increaseNumber(int by) { _number += by; signal_number_changed.emit(by); } int getNumber() const { return _number; } sigc::signal<void (int)> signal_number_changed; private: int _number = 0; }; std::ostream &operator<<(std::ostream &o, Emitter const &e) { o << e.getNumber(); return o; }
Several Inkscape classes use private signal attributes and define accessor methods for connecting slots, but this prevents you from using some advanced features. Wrapping them all in accessors isn't worth the hassle.
signal is a class template. It takes a function type parameter defining the types of parameters passed to each slot when the signal is emitted, and the type that the slot must return.
Connecting to signals
The signal class has a connect method, which takes a slot and adds it to the list of the signal's slots. Any function object can be used as a slot, including any lambda. Raw function pointers and member function pointers can also be used, provided they are wrapped as function objects using sigc::ptr_fun() and sigc::mem_fun() respectively. Here is a typical example:
class Receiver { public: Receiver(Emitter &emitter) { emitter.signal_number_changed.connect( sigc::mem_fun(*this, &Receiver::_handleNumberChange) ); } private: void _handleNumberChange(int increment) { std::cout << "The number increased by " << increment << std::endl; } };
sigc::mem_fun() produces a function object that invokes a given method on the given object when it is called. Now whenever the number stored in the Emitter class changes, all Receiver objects created with this Emitter object will have the _handleNumberChange() method called.
Memory safety
The above example is only memory-safe if the lifetime of the Receiver can be guaranteed to end before the lifetime of the Emitter. Otherwise, there is nothing stopping the slot being invoked on a destructed Receiver. If this happens in our trivial example it will be harmless, but most of the time it will be a use-after-free, and a crash.
If you are used to Qt signals and slots, this will come as a surprise, because in Qt slots are automatically disconnected when objects are destroyed, making them memory-safe by default. There is no such guarantee in libsigc++; safety must instead be opted-into.
There are two ways to solve this problem.
Automatic disconnection
Make Receiver publically derive from sigc::trackable. Then use any of sigc::mem_fn(), sigc::track_obj() or sigc::track_object() to create slots. The slot will then be disconnected when the object is destroyed.
Here are some further comments and caveats with this approach:
- If you forget to make make Receiver inherit from sigc::trackable, then sigc::mem_fn() and sigc::track_obj() will silently fall back to being unsafe, which makes code that uses them fragile. By contrast, sigc::track_object() will refuse to compile. However, at the time of writing, the superior sigc::track_object() cannot be used yet due to not being supported on all build targets.
- Even with the above approach, signals will be disconnected during the destructor of the sigc::trackable base subobject, which happens after the destructor of the Receiver subobject. So it is entirely possible for a sufficiently complicated class hierarchy, written by many different authors and performing complex signal manipulations in its destructor, to invoke slots on destructed objects even when the best practices for sigc::trackable and sigc::track_obj() are followed. This is not an issue for simple classes, or for the approach of the next section.
Manual disconnection
The connect() function actually returns a sigc::connection object with a disconnect() method that can be used to disconnect the connection. We can use this to improve our example:
class Receiver { public: Receiver(Emitter &emitter) { _conn = emitter.signal_number_changed.connect( sigc::mem_fun(*this, &Receiver::_handleNumberChange) ); } ~Receiver() { _conn.disconnect(); } private: sigc::connection _conn; void _handleNumberChange(int increment) { std::cout << "The number increased by " << increment << std::endl; } };
However, when working with sigc::connection there are still a number of traps that make it extremely easy to write bugs:
- If you destroy a connection without disconnecting it, it will not automatically be disconnected.
- Similarly, if you overwrite a connection with another one, the original will not automatically be disconnected.
These errors are some of the most frequent sources of use-after-frees in Inkscape. The problem is that sigc::connection behaves a lot like a raw pointer, with disconnect() being another way of spelling delete. What is needed is something that behaves more like a std::unique_ptr. For that reason, Inkscape has a RAII wrapper around sigc::connection called Inkscape::auto_connection that does not have these issues, and should be preferred wherever possible.
Adaptors
As you can see, we cannot directly access the object that emitted the signal from the handling method. You might think that changing the signal definition in the Emitter class to
sigc::signal<void (Emitter &, int)> signal_number_changed;
is the way to go. However, there are a few problems: first, the Emitter parameter for this slot is always the same (when you connect to another Emitter object from the same Receiver, you are creating a different slot stored in a different Emitter object). Moreover, it may be possible that some handlers only need the information about the number change - for instance, when you derive a class from Emitter and connect to the signals of the superclass, you already have the pointer to the superclass instance in this. There is a much better way to solve this problem. Another type of slots that libsigc++ provides are called adaptors - they allow you to change the type signature of your handler function by binding constant parameters to its invocation, ignoring some parameters supplied by the signal, and more.
Binding parameters
Let's say you want to print the name of the emitter that had its number increased. Instead of modifying the signal signature (a thing you sometimes cannot do, for example when you connect to a GTK-- signal), you can bind a parameter to your functor. First you need to modify the handler function:
void _handleNumberChange(Emitter &e, int by) { std::cout << "Emitter " << e.getName() << " increased number by " << by << std::endl; }
then bind the first parameter like this.
emitter.signal_number_changed.connect( sigc::bind<0>( sigc::mem_fun(*this, &Receiver::_handleNumberChange), emitter) ) );
sigc::bind takes an integer template parameter that tells it the zero-based index of the parameter it should bind. 0 denotes the first parameter, and the default value of -1 tells it to bind the last parameter. After this, your handler function will be called with the first parameter holding a reference to the object that emitted the signal.
Hiding parameters
Let's say you only want to print the name of the emitter and the current number, and you're not interested in how much it increased. You can then hide some of the signal's parameters.
void _handleNumberChange(Emitter &e) { std::cout << "Emitter << e.getName() << " changed its number." << std::endl; }
emitter.signal_number_changed.connect( sigc::hide( sigc::bind<0>( sigc::mem_fun(*this, &Receiver::_handleNumberChange), emitter) ) ) );
sigc::hide takes a template parameter just like sigc::bind. Here we use its default value. Also notice that you can easily chain adaptors. This powerful technique allows you to bind handlers to signals even if they weren't designed for each other.
Using the functors sigc::bind_return and sigc::hide_return you can also manipulate the return type. The first one changes to return type of the slot to that of a constant bound parameter. The second ignores the return value and create a slot returning void.
Note that there is an inversion of meaning: you use sigc::bind to supply something that isn't present in the signal, and sigc::hide to ignore something that isn't needed by the slot. With return adaptors, it's the other way around: you use sigc::bind_return to supply something that isn't present in the slot, and sigc::hide_return to ignore something that isn't needed by the signal.
Advanced: reordering parameters
Another powerful type of adaptor is sigc::group. It allows you to execute arbitrary mathematical expressions on the parameter list before pasing it to the handler. Here is an example.
class SomeGeometricObject { ... sigc::signal< void, Geom::Point const & /* old_position */, Geom::point const & /* new_position */ > signal_position_changed;
void _handlePositionChange(Geom::Point const &delta) { std::cout << "Object moved by" << ": X=" << delta[Geom::X] << ", Y=" << delta[Geom::Y] << std::endl; }
some_object.signal_position_changed.connect( sigc::group( sigc::mem_fun(*this, &Receiver::_handlePositionChange), sigc::_2 - sigc::_1 ) );
The objects sigc::_1 and sigc::_2 are called lambda expressions, and are used to denote the first and second parameter of the signal. The expression will be evaluated at slot call time, thanks to operator overloading. Note however that if you use variables other than lambdas, their values will be evaluated at connection time. To use references to variables instead of their current values, use sigc::ref.
Return values of signals
Signals can return a value. By default, they return the value returned from the last evaluated slot, or the default value for this return type of there was none (normally zero for numerical types, NULL for pointers, false for booleans, etc.).
signal<double> signal_get_length; double length_from_last_slot = signal_get_length.emit();
This is not ideal, because you lose data from other slots. libsigc++ provides a powerful mechanism for handling return values from multiple slots, called accumulators.
Accumulators
Accumulator is a small class template that defines a signal's return type and a call function that evaluates the slot list of a signal. Here is an example accumulator that calculates the sum of the values returned by slots.
template<class T> struct sum_accumulator { typedef T result_type; template<class I> result_type operator()(I first, I last) { T sum = 0; for (; first != last; first ++) sum += *first; return sum; }
};
There a few important things to notice:
- Each accumulator must have two public members: a type called result_type which defines the return value of the signal's emit() function, and an overloaded operator(), taking two arguments of some type (the type in question is called sigc::internal::slot_iterator_buf, but it's better to just use a template).
- The parameters of the function call operator are two iterators, pointing to the start and the end of the slot list. The expression *i evaluates the slot (i.e. calls it and returns the result).
- It is safe to write things like sum += (*i) * (*i); because the results are buffered - each slot is evaluated at most once in each signal emission.
To use the accumulator, you need to changed the signal definition a bit.
sigc::signal<double>::accumulated<sum_accumulator<int> > signal_get_length;
double sum_of_lengths = signal_get_length.emit();
Now sum_of_lengths contains the sum of return values from all the slots.
WARNING: There is an old bug in libsigc++ that makes accumulators severely broken, because their result type must be implicitly convertible to and from the slot return type. See this GNOME bug. It will be fixed in the next libsigc++ release. --Tweenk 09:27, 31 December 2009 (UTC)
Conventions and tips
- Your signals should only have parameters that can change with each invocation. For example, a position change signal could have the old and new positions as the parameters. In particular, signals should not have the emitting object as a parameter. The proper way to connect to a signal from a different object is to use sigc::bind like this:
class SomeObject { public: sigc::signal<void> signal_update; ... }; class OtherObject { ... void onUpdate(SomeObject &so); }; ... some_object.signal_update.connect( sigc::bind<0>( sigc::mem_fun(other_object, &OtherObject::onUpdate), some_object));
In the example, a reference is bound (because the SomeObject parameter will never need to be NULL), but you can also use a pointer.
- Use the C++ wrappers for GTK+ as a reference for good signal parameter choices.
- The memory and performance overhead of adding signals to an object is quite small - most things are handled at compile time thanks to the use of templates.
- When using Boost shared pointers, you cannot bind them as the "emitting object" parameter to slots. That's because the shared pointer will be copied into the object's slot list, keeping it in memory indefinitely. You need to use a weak pointer, which won't prevent the object's desctruction, but can be used to obtain a regular shared pointer. See Shared pointers for a more detailed guide.
See Also
libsigc++ reference documentation Gnome bug #586436 - epic accumulator fail