• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<?xml version="1.0" encoding="utf-8"?>
2<!DOCTYPE section PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN"
3  "http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
4<!--
5Copyright Douglas Gregor 2001-2004
6Copyright Frank Mori Hess 2007-2009
7
8Distributed under the Boost Software License, Version 1.0. (See accompanying
9file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
10-->
11<section last-revision="$Date: 2007-06-12 14:01:23 -0400 (Tue, 12 Jun 2007) $" id="signals2.rationale">
12  <title>Design Rationale</title>
13
14  <using-namespace name="boost::signals2"/>
15  <using-namespace name="boost"/>
16  <using-class name="boost::signals2::signal"/>
17
18  <section>
19    <title>User-level Connection Management</title>
20
21    <para> Users need to have fine control over the connection of
22    signals to slots and their eventual disconnection. The primary approach
23    taken by Boost.Signals2 is to return a
24    <code><classname>signals2::connection</classname></code> object that enables
25    connected/disconnected query, manual disconnection, and an
26    automatic disconnection on destruction mode (<classname>signals2::scoped_connection</classname>).
27    In addition, two other interfaces are supported by the
28    <methodname alt="signal::disconnect">signal::disconnect</methodname> overloaded method:</para>
29
30    <itemizedlist>
31      <listitem>
32        <para><emphasis role="bold">Pass slot to
33        disconnect</emphasis>: in this interface model, the
34        disconnection of a slot connected with
35        <code>sig.<methodname>connect</methodname>(typeof(sig)::slot_type(slot_func))</code> is
36        performed via
37        <code>sig.<methodname>disconnect</methodname>(slot_func)</code>. Internally,
38        a linear search using slot comparison is performed and the
39        slot, if found, is removed from the list. Unfortunately,
40        querying connectedness ends up as a
41        linear-time operation.</para>
42      </listitem>
43
44      <listitem>
45        <para><emphasis role="bold">Pass a token to
46        disconnect</emphasis>: this approach identifies slots with a
47        token that is easily comparable (e.g., a string), enabling
48        slots to be arbitrary function objects. While this approach is
49        essentially equivalent to the connection approach taken by Boost.Signals2,
50        it is possibly more error-prone for several reasons:</para>
51
52        <itemizedlist>
53          <listitem>
54            <para>Connections and disconnections must be paired, so
55            the problem becomes similar to the problems incurred when
56            pairing <code>new</code> and <code>delete</code> for
57            dynamic memory allocation. While errors of this sort would
58            not be catastrophic for a signals and slots
59            implementation, their detection is generally
60            nontrivial.</para>
61          </listitem>
62
63          <listitem>
64            <para>If tokens are not unique, two slots may have
65            the same name and be indistinguishable. In
66            environments where many connections will be made
67            dynamically, name generation becomes an additional task
68            for the user.</para>
69          </listitem>
70        </itemizedlist>
71
72        <para> This type of interface is supported in Boost.Signals2
73        via the slot grouping mechanism, and the overload of
74        <methodname alt="signal::disconnect">signal::disconnect</methodname>
75        which takes an argument of the signal's <code>Group</code> type.</para>
76      </listitem>
77    </itemizedlist>
78  </section>
79
80  <section>
81    <title>Automatic Connection Management</title>
82
83    <para>Automatic connection management in Signals2
84      depends on the use of <classname>boost::shared_ptr</classname> to
85      manage the lifetimes of tracked objects.  This is differs from
86      the original Boost.Signals library, which instead relied on derivation
87      from the <code><classname>boost::signals::trackable</classname></code> class.
88      The library would be
89      notified of an object's destruction by the
90      <code><classname>boost::signals::trackable</classname></code> destructor.
91    </para>
92    <para>Unfortunately, the <code><classname>boost::signals::trackable</classname></code>
93      scheme cannot be made thread safe due
94      to destructor ordering.  The destructor of an class derived from
95      <code><classname>boost::signals::trackable</classname></code> will always be
96      called before the destructor of the base <code><classname>boost::signals::trackable</classname></code>
97      class.  However, for thread-safety the connection between the signal and object
98      needs to be disconnected before the object runs its destructors.
99      Otherwise, if an object being destroyed
100      in one thread is connected to a signal concurrently
101      invoking in another thread, the signal may call into
102      a partially destroyed object.
103    </para>
104    <para>We solve this problem by requiring that tracked objects be
105      managed by <classname>shared_ptr</classname>.  Slots keep a
106      <classname>weak_ptr</classname> to every object the slot depends
107      on.  Connections to a slot are disconnected when any of its tracked
108      <classname>weak_ptr</classname>s expire.  Additionally, signals
109      create their own temporary <classname>shared_ptr</classname>s to
110      all of a slot's tracked objects prior to invoking the slot.  This
111      insures none of the tracked objects destruct in mid-invocation.
112    </para>
113    <para>The new connection management scheme has the advantage of being
114      non-intrusive.  Objects of any type may be tracked using the
115      <classname>shared_ptr</classname>/<classname>weak_ptr</classname> scheme.  The old
116      <code><classname>boost::signals::trackable</classname></code>
117      scheme requires the tracked objects to be derived from the <code>trackable</code>
118      base class, which is not always practical when interacting
119      with classes from 3rd party libraries.
120    </para>
121  </section>
122
123  <section>
124    <title><code>optional_last_value</code> as the Default Combiner</title>
125    <para>
126      The default combiner for Boost.Signals2 has changed from the <code>last_value</code>
127      combiner used by default in the original Boost.Signals library.
128      This is because <code>last_value</code> requires that at least 1 slot be
129      connected to the signal when it is invoked (except for the <code>last_value&lt;void&gt;</code> specialization).
130      In a multi-threaded environment where signal invocations and slot connections
131      and disconnections may be happening concurrently, it is difficult
132      to fulfill this requirement.  When using <classname>optional_last_value</classname>,
133      there is no requirement for slots to be connected when a signal
134      is invoked, since in that case the combiner may simply return an empty
135      <classname>boost::optional</classname>.
136    </para>
137  </section>
138  <section>
139    <title>Combiner Interface</title>
140
141    <para> The Combiner interface was chosen to mimic a call to an
142    algorithm in the C++ standard library. It is felt that by viewing
143    slot call results as merely a sequence of values accessed by input
144    iterators, the combiner interface would be most natural to a
145    proficient C++ programmer. Competing interface design generally
146    required the combiners to be constructed to conform to an
147    interface that would be customized for (and limited to) the
148    Signals2 library. While these interfaces are generally enable more
149    straighforward implementation of the signals &amp; slots
150    libraries, the combiners are unfortunately not reusable (either in
151    other signals &amp; slots libraries or within other generic
152    algorithms), and the learning curve is steepened slightly to learn
153    the specific combiner interface.</para>
154
155    <para> The Signals2 formulation of combiners is based on the
156    combiner using the "pull" mode of communication, instead of the
157    more complex "push" mechanism. With a "pull" mechanism, the
158    combiner's state can be kept on the stack and in the program
159    counter, because whenever new data is required (i.e., calling the
160    next slot to retrieve its return value), there is a simple
161    interface to retrieve that data immediately and without returning
162    from the combiner's code. Contrast this with the "push" mechanism,
163    where the combiner must keep all state in class members because
164    the combiner's routines will be invoked for each signal
165    called. Compare, for example, a combiner that returns the maximum
166    element from calling the slots. If the maximum element ever
167    exceeds 100, no more slots are to be called.</para>
168
169    <informaltable>
170      <tgroup cols="2" align="left">
171        <thead>
172          <row>
173            <entry><para>Pull</para></entry>
174            <entry><para>Push</para></entry>
175          </row>
176        </thead>
177        <tbody>
178          <row>
179            <entry>
180<programlisting>
181struct pull_max {
182  typedef int result_type;
183
184  template&lt;typename InputIterator&gt;
185  result_type operator()(InputIterator first,
186                         InputIterator last)
187  {
188    if (first == last)
189      throw std::runtime_error("Empty!");
190
191    int max_value = *first++;
192    while(first != last &amp;&amp; *first &lt;= 100) {
193      if (*first &gt; max_value)
194        max_value = *first;
195      ++first;
196    }
197
198    return max_value;
199  }
200};
201</programlisting>
202</entry>
203            <entry>
204<programlisting>
205struct push_max {
206  typedef int result_type;
207
208  push_max() : max_value(), got_first(false) {}
209
210  // returns false when we want to stop
211  bool operator()(int result) {
212    if (result &gt; 100)
213      return false;
214
215    if (!got_first) {
216      got_first = true;
217      max_value = result;
218      return true;
219    }
220
221    if (result &gt; max_value)
222      max_value = result;
223
224    return true;
225  }
226
227  int get_value() const
228  {
229    if (!got_first)
230      throw std::runtime_error("Empty!");
231    return max_value;
232  }
233
234private:
235  int  max_value;
236  bool got_first;
237};
238</programlisting>
239</entry>
240          </row>
241        </tbody>
242      </tgroup>
243    </informaltable>
244
245    <para>There are several points to note in these examples. The
246    "pull" version is a reusable function object that is based on an
247    input iterator sequence with an integer <code>value_type</code>,
248    and is very straightforward in design. The "push" model, on the
249    other hand, relies on an interface specific to the caller and is
250    not generally reusable. It also requires extra state values to
251    determine, for instance, if any elements have been
252    received. Though code quality and ease-of-use is generally
253    subjective, the "pull" model is clearly shorter and more reusable
254    and will often be construed as easier to write and understand,
255    even outside the context of a signals &amp; slots library.</para>
256
257    <para> The cost of the "pull" combiner interface is paid in the
258    implementation of the Signals2 library itself. To correctly handle
259    slot disconnections during calls (e.g., when the dereference
260    operator is invoked), one must construct the iterator to skip over
261    disconnected slots. Additionally, the iterator must carry with it
262    the set of arguments to pass to each slot (although a reference to
263    a structure containing those arguments suffices), and must cache
264    the result of calling the slot so that multiple dereferences don't
265    result in multiple calls. This apparently requires a large degree
266    of overhead, though if one considers the entire process of
267    invoking slots one sees that the overhead is nearly equivalent to
268    that in the "push" model, but we have inverted the control
269    structures to make iteration and dereference complex (instead of
270    making combiner state-finding complex).</para>
271  </section>
272
273  <section>
274    <title>Connection Interfaces: +=  operator</title>
275
276    <para> Boost.Signals2 supports a connection syntax with the form
277    <code>sig.<methodname>connect</methodname>(slot)</code>, but a
278    more terse syntax <code>sig += slot</code> has been suggested (and
279    has been used by other signals &amp; slots implementations). There
280    are several reasons as to why this syntax has been
281    rejected:</para>
282
283    <itemizedlist>
284      <listitem>
285        <para><emphasis role="bold">It's unnecessary</emphasis>: the
286        connection syntax supplied by Boost.Signals2 is no less
287        powerful that that supplied by the <code>+=</code>
288        operator. The savings in typing (<code>connect()</code>
289        vs. <code>+=</code>) is essentially negligible. Furthermore,
290        one could argue that calling <code>connect()</code> is more
291        readable than an overload of <code>+=</code>.</para>
292      </listitem>
293      <listitem>
294        <para><emphasis role="bold">Ambiguous return type</emphasis>:
295        there is an ambiguity concerning the return value of the
296        <code>+=</code> operation: should it be a reference to the
297        signal itself, to enable <code>sig += slot1 += slot2</code>,
298        or should it return a
299        <code><classname>signals2::connection</classname></code> for the
300        newly-created signal/slot connection?</para>
301      </listitem>
302
303      <listitem>
304        <para><emphasis role="bold">Gateway to operators -=,
305        +</emphasis>: when one has added a connection operator
306        <code>+=</code>, it seems natural to have a disconnection
307        operator <code>-=</code>. However, this presents problems when
308        the library allows arbitrary function objects to implicitly
309        become slots, because slots are no longer comparable.  <!--
310        (see the discussion on this topic in User-level Connection
311        Management). --></para>
312
313        <para> The second obvious addition when one has
314        <code>operator+=</code> would be to add a <code>+</code>
315        operator that supports addition of multiple slots, followed by
316        assignment to a signal. However, this would require
317        implementing <code>+</code> such that it can accept any two
318        function objects, which is technically infeasible.</para>
319      </listitem>
320    </itemizedlist>
321  </section>
322  <section>
323    <title>Signals2 Mutex Classes</title>
324    <para>
325      The Boost.Signals2 library provides 2 mutex classes: <classname>boost::signals2::mutex</classname>,
326      and <classname>boost::signals2::dummy_mutex</classname>.  The motivation for providing
327      <classname>boost::signals2::mutex</classname> is simply that the <classname>boost::mutex</classname>
328      class provided by the Boost.Thread library currently requires linking to libboost_thread.
329      The <classname>boost::signals2::mutex</classname> class allows Signals2 to remain
330      a header-only library.  You may still choose to use <classname>boost::mutex</classname>
331      if you wish, by specifying it as the <code>Mutex</code> template type for your signals.
332    </para>
333    <para>
334      The <classname>boost::signals2::dummy_mutex</classname> class is provided to allow
335      performance sensitive single-threaded applications to minimize overhead by avoiding unneeded
336      mutex locking.
337    </para>
338  </section>
339  <section>
340    <title>Comparison with other Signal/Slot implementations</title>
341
342    <section>
343      <title>libsigc++</title>
344
345      <para> <ulink
346      url="http://libsigc.sourceforge.net">libsigc++</ulink> is a C++
347      signals &amp; slots library that originally started as part of
348      an initiative to wrap the C interfaces to <ulink
349      url="http://www.gtk.org">GTK</ulink> libraries in C++, and has
350      grown to be a separate library maintained by Karl Nelson. There
351      are many similarities between libsigc++ and Boost.Signals2, and
352      indeed the original Boost.Signals was strongly influenced by
353      Karl Nelson and libsigc++. A cursory inspection of each library will find a
354      similar syntax for the construction of signals and in the use of
355      connections. There
356      are some major differences in design that separate these
357      libraries:</para>
358
359      <itemizedlist>
360        <listitem>
361          <para><emphasis role="bold">Slot definitions</emphasis>:
362          slots in libsigc++ are created using a set of primitives
363          defined by the library. These primitives allow binding of
364          objects (as part of the library), explicit adaptation from
365          the argument and return types of the signal to the argument
366          and return types of the slot (libsigc++ is, by default, more
367          strict about types than Boost.Signals2).</para>
368        </listitem>
369
370        <listitem>
371          <para><emphasis role="bold">Combiner/Marshaller
372          interface</emphasis>: the equivalent to Boost.Signals2
373          combiners in libsigc++ are the marshallers. Marshallers are
374          similar to the "push" interface described in Combiner
375          Interface, and a proper treatment of the topic is given
376          there.</para>
377        </listitem>
378      </itemizedlist>
379    </section>
380
381    <section>
382      <title>.NET delegates</title>
383
384      <para> <ulink url="http://www.microsoft.com">Microsoft</ulink>
385      has introduced the .NET Framework and an associated set of
386      languages and language extensions, one of which is the
387      delegate. Delegates are similar to signals and slots, but they
388      are more limited than most C++ signals and slots implementations
389      in that they:</para>
390
391      <itemizedlist>
392        <listitem>
393          <para>Require exact type matches between a delegate and what
394          it is calling.</para>
395        </listitem>
396
397        <listitem><para>Only return the result of the last target called, with no option for customization.</para></listitem>
398        <listitem>
399          <para>Must call a method with <code>this</code> already
400          bound.</para>
401        </listitem>
402      </itemizedlist>
403    </section>
404  </section>
405</section>
406