
in old Boost.Signals when I meant them to link to Boost.Signals2 classes. Removed deprecated stuff from documentation and updated wrt to changes made for the sake of the variadic template implementation. Added section on changes to interface which will appear in 1.40. [SVN r53548]
406 lines
18 KiB
XML
406 lines
18 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!DOCTYPE section PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN"
|
|
"http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
|
|
<!--
|
|
Copyright Douglas Gregor 2001-2004
|
|
Copyright Frank Mori Hess 2007-2009
|
|
|
|
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
-->
|
|
<section last-revision="$Date: 2007-06-12 14:01:23 -0400 (Tue, 12 Jun 2007) $" id="signals2.rationale">
|
|
<title>Design Rationale</title>
|
|
|
|
<using-namespace name="boost::signals2"/>
|
|
<using-namespace name="boost"/>
|
|
<using-class name="boost::signals2::signal"/>
|
|
|
|
<section>
|
|
<title>User-level Connection Management</title>
|
|
|
|
<para> Users need to have fine control over the connection of
|
|
signals to slots and their eventual disconnection. The primary approach
|
|
taken by Boost.Signals2 is to return a
|
|
<code><classname>signals2::connection</classname></code> object that enables
|
|
connected/disconnected query, manual disconnection, and an
|
|
automatic disconnection on destruction mode (<classname>signals2::scoped_connection</classname>).
|
|
In addition, two other interfaces are supported by the
|
|
<methodname alt="signal::disconnect">signal::disconnect</methodname> overloaded method:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><emphasis role="bold">Pass slot to
|
|
disconnect</emphasis>: in this interface model, the
|
|
disconnection of a slot connected with
|
|
<code>sig.<methodname>connect</methodname>(typeof(sig)::slot_type(slot_func))</code> is
|
|
performed via
|
|
<code>sig.<methodname>disconnect</methodname>(slot_func)</code>. Internally,
|
|
a linear search using slot comparison is performed and the
|
|
slot, if found, is removed from the list. Unfortunately,
|
|
querying connectedness ends up as a
|
|
linear-time operation.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para><emphasis role="bold">Pass a token to
|
|
disconnect</emphasis>: this approach identifies slots with a
|
|
token that is easily comparable (e.g., a string), enabling
|
|
slots to be arbitrary function objects. While this approach is
|
|
essentially equivalent to the connection approach taken by Boost.Signals2,
|
|
it is possibly more error-prone for several reasons:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>Connections and disconnections must be paired, so
|
|
the problem becomes similar to the problems incurred when
|
|
pairing <code>new</code> and <code>delete</code> for
|
|
dynamic memory allocation. While errors of this sort would
|
|
not be catastrophic for a signals and slots
|
|
implementation, their detection is generally
|
|
nontrivial.</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>If tokens are not unique, two slots may have
|
|
the same name and be indistinguishable. In
|
|
environments where many connections will be made
|
|
dynamically, name generation becomes an additional task
|
|
for the user.</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para> This type of interface is supported in Boost.Signals2
|
|
via the slot grouping mechanism, and the overload of
|
|
<methodname alt="signal::disconnect">signal::disconnect</methodname>
|
|
which takes an argument of the signal's <code>Group</code> type.</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Automatic Connection Management</title>
|
|
|
|
<para>Automatic connection management in Signals2
|
|
depends on the use of <classname>boost::shared_ptr</classname> to
|
|
manage the lifetimes of tracked objects. This is differs from
|
|
the original Boost.Signals library, which instead relied on derivation
|
|
from the <code><classname>boost::signals::trackable</classname></code> class.
|
|
The library would be
|
|
notified of an object's destruction by the
|
|
<code><classname>boost::signals::trackable</classname></code> destructor.
|
|
</para>
|
|
<para>Unfortunately, the <code><classname>boost::signals::trackable</classname></code>
|
|
scheme cannot be made thread safe due
|
|
to destructor ordering. The destructor of an class derived from
|
|
<code><classname>boost::signals::trackable</classname></code> will always be
|
|
called before the destructor of the base <code><classname>boost::signals::trackable</classname></code>
|
|
class. However, for thread-safety the connection between the signal and object
|
|
needs to be disconnected before the object runs its destructors.
|
|
Otherwise, if an object being destroyed
|
|
in one thread is connected to a signal concurrently
|
|
invoking in another thread, the signal may call into
|
|
a partially destroyed object.
|
|
</para>
|
|
<para>We solve this problem by requiring that tracked objects be
|
|
managed by <classname>shared_ptr</classname>. Slots keep a
|
|
<classname>weak_ptr</classname> to every object the slot depends
|
|
on. Connections to a slot are disconnected when any of its tracked
|
|
<classname>weak_ptr</classname>s expire. Additionally, signals
|
|
create their own temporary <classname>shared_ptr</classname>s to
|
|
all of a slot's tracked objects prior to invoking the slot. This
|
|
insures none of the tracked objects destruct in mid-invocation.
|
|
</para>
|
|
<para>The new connection management scheme has the advantage of being
|
|
non-intrusive. Objects of any type may be tracked using the
|
|
<classname>shared_ptr</classname>/<classname>weak_ptr</classname> scheme. The old
|
|
<code><classname>boost::signals::trackable</classname></code>
|
|
scheme requires the tracked objects to be derived from the <code>trackable</code>
|
|
base class, which is not always practical when interacting
|
|
with classes from 3rd party libraries.
|
|
</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title><code>optional_last_value</code> as the Default Combiner</title>
|
|
<para>
|
|
The default combiner for Boost.Signals2 has changed from the <code>last_value</code>
|
|
combiner used by default in the original Boost.Signals library.
|
|
This is because <code>last_value</code> requires that at least 1 slot be
|
|
connected to the signal when it is invoked (except for the <code>last_value<void></code> specialization).
|
|
In a multi-threaded environment where signal invocations and slot connections
|
|
and disconnections may be happening concurrently, it is difficult
|
|
to fulfill this requirement. When using <classname>optional_last_value</classname>,
|
|
there is no requirement for slots to be connected when a signal
|
|
is invoked, since in that case the combiner may simply return an empty
|
|
<classname>boost::optional</classname>.
|
|
</para>
|
|
</section>
|
|
<section>
|
|
<title>Combiner Interface</title>
|
|
|
|
<para> The Combiner interface was chosen to mimic a call to an
|
|
algorithm in the C++ standard library. It is felt that by viewing
|
|
slot call results as merely a sequence of values accessed by input
|
|
iterators, the combiner interface would be most natural to a
|
|
proficient C++ programmer. Competing interface design generally
|
|
required the combiners to be constructed to conform to an
|
|
interface that would be customized for (and limited to) the
|
|
Signals2 library. While these interfaces are generally enable more
|
|
straighforward implementation of the signals & slots
|
|
libraries, the combiners are unfortunately not reusable (either in
|
|
other signals & slots libraries or within other generic
|
|
algorithms), and the learning curve is steepened slightly to learn
|
|
the specific combiner interface.</para>
|
|
|
|
<para> The Signals2 formulation of combiners is based on the
|
|
combiner using the "pull" mode of communication, instead of the
|
|
more complex "push" mechanism. With a "pull" mechanism, the
|
|
combiner's state can be kept on the stack and in the program
|
|
counter, because whenever new data is required (i.e., calling the
|
|
next slot to retrieve its return value), there is a simple
|
|
interface to retrieve that data immediately and without returning
|
|
from the combiner's code. Contrast this with the "push" mechanism,
|
|
where the combiner must keep all state in class members because
|
|
the combiner's routines will be invoked for each signal
|
|
called. Compare, for example, a combiner that returns the maximum
|
|
element from calling the slots. If the maximum element ever
|
|
exceeds 100, no more slots are to be called.</para>
|
|
|
|
<informaltable>
|
|
<tgroup cols="2" align="left">
|
|
<thead>
|
|
<row>
|
|
<entry><para>Pull</para></entry>
|
|
<entry><para>Push</para></entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>
|
|
<programlisting>
|
|
struct pull_max {
|
|
typedef int result_type;
|
|
|
|
template<typename InputIterator>
|
|
result_type operator()(InputIterator first,
|
|
InputIterator last)
|
|
{
|
|
if (first == last)
|
|
throw std::runtime_error("Empty!");
|
|
|
|
int max_value = *first++;
|
|
while(first != last && *first <= 100) {
|
|
if (*first > max_value)
|
|
max_value = *first;
|
|
++first;
|
|
}
|
|
|
|
return max_value;
|
|
}
|
|
};
|
|
</programlisting>
|
|
</entry>
|
|
<entry>
|
|
<programlisting>
|
|
struct push_max {
|
|
typedef int result_type;
|
|
|
|
push_max() : max_value(), got_first(false) {}
|
|
|
|
// returns false when we want to stop
|
|
bool operator()(int result) {
|
|
if (result > 100)
|
|
return false;
|
|
|
|
if (!got_first) {
|
|
got_first = true;
|
|
max_value = result;
|
|
return true;
|
|
}
|
|
|
|
if (result > max_value)
|
|
max_value = result;
|
|
|
|
return true;
|
|
}
|
|
|
|
int get_value() const
|
|
{
|
|
if (!got_first)
|
|
throw std::runtime_error("Empty!");
|
|
return max_value;
|
|
}
|
|
|
|
private:
|
|
int max_value;
|
|
bool got_first;
|
|
};
|
|
</programlisting>
|
|
</entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</informaltable>
|
|
|
|
<para>There are several points to note in these examples. The
|
|
"pull" version is a reusable function object that is based on an
|
|
input iterator sequence with an integer <code>value_type</code>,
|
|
and is very straightforward in design. The "push" model, on the
|
|
other hand, relies on an interface specific to the caller and is
|
|
not generally reusable. It also requires extra state values to
|
|
determine, for instance, if any elements have been
|
|
received. Though code quality and ease-of-use is generally
|
|
subjective, the "pull" model is clearly shorter and more reusable
|
|
and will often be construed as easier to write and understand,
|
|
even outside the context of a signals & slots library.</para>
|
|
|
|
<para> The cost of the "pull" combiner interface is paid in the
|
|
implementation of the Signals2 library itself. To correctly handle
|
|
slot disconnections during calls (e.g., when the dereference
|
|
operator is invoked), one must construct the iterator to skip over
|
|
disconnected slots. Additionally, the iterator must carry with it
|
|
the set of arguments to pass to each slot (although a reference to
|
|
a structure containing those arguments suffices), and must cache
|
|
the result of calling the slot so that multiple dereferences don't
|
|
result in multiple calls. This apparently requires a large degree
|
|
of overhead, though if one considers the entire process of
|
|
invoking slots one sees that the overhead is nearly equivalent to
|
|
that in the "push" model, but we have inverted the control
|
|
structures to make iteration and dereference complex (instead of
|
|
making combiner state-finding complex).</para>
|
|
</section>
|
|
|
|
<section>
|
|
<title>Connection Interfaces: += operator</title>
|
|
|
|
<para> Boost.Signals2 supports a connection syntax with the form
|
|
<code>sig.<methodname>connect</methodname>(slot)</code>, but a
|
|
more terse syntax <code>sig += slot</code> has been suggested (and
|
|
has been used by other signals & slots implementations). There
|
|
are several reasons as to why this syntax has been
|
|
rejected:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><emphasis role="bold">It's unnecessary</emphasis>: the
|
|
connection syntax supplied by Boost.Signals2 is no less
|
|
powerful that that supplied by the <code>+=</code>
|
|
operator. The savings in typing (<code>connect()</code>
|
|
vs. <code>+=</code>) is essentially negligible. Furthermore,
|
|
one could argue that calling <code>connect()</code> is more
|
|
readable than an overload of <code>+=</code>.</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para><emphasis role="bold">Ambiguous return type</emphasis>:
|
|
there is an ambiguity concerning the return value of the
|
|
<code>+=</code> operation: should it be a reference to the
|
|
signal itself, to enable <code>sig += slot1 += slot2</code>,
|
|
or should it return a
|
|
<code><classname>signals2::connection</classname></code> for the
|
|
newly-created signal/slot connection?</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para><emphasis role="bold">Gateway to operators -=,
|
|
+</emphasis>: when one has added a connection operator
|
|
<code>+=</code>, it seems natural to have a disconnection
|
|
operator <code>-=</code>. However, this presents problems when
|
|
the library allows arbitrary function objects to implicitly
|
|
become slots, because slots are no longer comparable. <!--
|
|
(see the discussion on this topic in User-level Connection
|
|
Management). --></para>
|
|
|
|
<para> The second obvious addition when one has
|
|
<code>operator+=</code> would be to add a <code>+</code>
|
|
operator that supports addition of multiple slots, followed by
|
|
assignment to a signal. However, this would require
|
|
implementing <code>+</code> such that it can accept any two
|
|
function objects, which is technically infeasible.</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
<section>
|
|
<title>Signals2 Mutex Classes</title>
|
|
<para>
|
|
The Boost.Signals2 library provides 2 mutex classes: <classname>boost::signals2::mutex</classname>,
|
|
and <classname>boost::signals2::dummy_mutex</classname>. The motivation for providing
|
|
<classname>boost::signals2::mutex</classname> is simply that the <classname>boost::mutex</classname>
|
|
class provided by the Boost.Thread library currently requires linking to libboost_thread.
|
|
The <classname>boost::signals2::mutex</classname> class allows Signals2 to remain
|
|
a header-only library. You may still choose to use <classname>boost::mutex</classname>
|
|
if you wish, by specifying it as the <code>Mutex</code> template type for your signals.
|
|
</para>
|
|
<para>
|
|
The <classname>boost::signals2::dummy_mutex</classname> class is provided to allow
|
|
performance sensitive single-threaded applications to minimize overhead by avoiding unneeded
|
|
mutex locking.
|
|
</para>
|
|
</section>
|
|
<section>
|
|
<title>Comparison with other Signal/Slot implementations</title>
|
|
|
|
<section>
|
|
<title>libsigc++</title>
|
|
|
|
<para> <ulink
|
|
url="http://libsigc.sourceforge.net">libsigc++</ulink> is a C++
|
|
signals & slots library that originally started as part of
|
|
an initiative to wrap the C interfaces to <ulink
|
|
url="http://www.gtk.org">GTK</ulink> libraries in C++, and has
|
|
grown to be a separate library maintained by Karl Nelson. There
|
|
are many similarities between libsigc++ and Boost.Signals2, and
|
|
indeed the original Boost.Signals was strongly influenced by
|
|
Karl Nelson and libsigc++. A cursory inspection of each library will find a
|
|
similar syntax for the construction of signals and in the use of
|
|
connections. There
|
|
are some major differences in design that separate these
|
|
libraries:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para><emphasis role="bold">Slot definitions</emphasis>:
|
|
slots in libsigc++ are created using a set of primitives
|
|
defined by the library. These primitives allow binding of
|
|
objects (as part of the library), explicit adaptation from
|
|
the argument and return types of the signal to the argument
|
|
and return types of the slot (libsigc++ is, by default, more
|
|
strict about types than Boost.Signals2).</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para><emphasis role="bold">Combiner/Marshaller
|
|
interface</emphasis>: the equivalent to Boost.Signals2
|
|
combiners in libsigc++ are the marshallers. Marshallers are
|
|
similar to the "push" interface described in Combiner
|
|
Interface, and a proper treatment of the topic is given
|
|
there.</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
|
|
<section>
|
|
<title>.NET delegates</title>
|
|
|
|
<para> <ulink url="http://www.microsoft.com">Microsoft</ulink>
|
|
has introduced the .NET Framework and an associated set of
|
|
languages and language extensions, one of which is the
|
|
delegate. Delegates are similar to signals and slots, but they
|
|
are more limited than most C++ signals and slots implementations
|
|
in that they:</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>Require exact type matches between a delegate and what
|
|
it is calling.</para>
|
|
</listitem>
|
|
|
|
<listitem><para>Only return the result of the last target called, with no option for customization.</para></listitem>
|
|
<listitem>
|
|
<para>Must call a method with <code>this</code> already
|
|
bound.</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</section>
|
|
</section>
|
|
</section>
|