Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: downloads/boost_1_34_1/libs/signals/doc/rationale.xml @ 35

Last change on this file since 35 was 29, checked in by landauf, 16 years ago

updated boost from 1_33_1 to 1_34_1

File size: 19.1 KB
Line 
1<?xml version="1.0" encoding="utf-8"?>
2<!--
3   Copyright (c) 2002 Douglas Gregor <doug.gregor -at- gmail.com>
4 
5   Distributed under the Boost Software License, Version 1.0.
6   (See accompanying file LICENSE_1_0.txt or copy at
7   http://www.boost.org/LICENSE_1_0.txt)
8  -->
9<!DOCTYPE section PUBLIC "-//Boost//DTD BoostBook XML V1.0//EN"
10  "http://www.boost.org/tools/boostbook/dtd/boostbook.dtd">
11<section last-revision="$Date: 2006/11/03 19:45:40 $">
12  <title>Design Rationale</title>
13
14  <using-namespace name="boost"/>
15  <using-namespace name="boost::signals"/>
16  <using-class name="boost::signalN"/>
17
18  <section>
19    <title>Choice of Slot Definitions</title> 
20
21    <para> The definition of a slot differs amongst signals and slots
22    libraries. Within Boost.Signals, a slot is defined in a very loose
23    manner: it can be any function object that is callable given
24    parameters of the types specified by the signal, and whose return
25    value is convertible to the result type expected by the
26    signal. However, alternative definitions have associated pros and
27    cons that were considered prior to the construction of
28    Boost.Signals.</para>
29
30    <itemizedlist>
31      <listitem>
32        <para><emphasis role="bold">Slots derive from a specific base
33        class</emphasis>: generally a scheme such as this will require
34        all user-defined slots to derive from some library-specified
35        <code>Slot</code> abstract class that defines a virtual
36        function calling the slot. Adaptors can be used to convert a
37        definition such as this to a definition similar to that used
38        by Boost.Signals, but the use of a large number of small
39        adaptor classes containing virtual functions has been found to
40        cause an unacceptable increase in the size of executables
41        (polymorphic class types require more code than
42        non-polymorphic types).</para>
43
44        <para> This approach does have the benefit of simplicity of
45        implementation and user interface, from an object-oriented
46        perspective.</para>
47      </listitem>
48
49      <listitem>
50        <para><emphasis role="bold">Slots constructed from a set of
51        primitives</emphasis>: in this scheme the slot can have a
52        limited set of types (often derived from a common abstract
53        base class) that are constructed from some library-defined set
54        of primitives that often include conversions from free
55        function pointers and member function pointers, and a limited
56        set of binding capabilities. Such an approach is reasonably
57        simple and cover most common cases, but it does not allow a
58        large degree of flexibility in slot construction. Libraries
59        for function object composition have become quite advanced and
60        it is out of the scope of a signals and slots library to
61        encorporate such enhancements. Thus Boost.Signals does not
62        include argument binding or function object composition
63        primitives, but instead provides a hook (via the
64        <code><functionname>visit_each</functionname></code>
65        mechanism) that allows existing binder/composition libraries
66        to provide the necessary information to Signals.</para>
67      </listitem>
68    </itemizedlist>
69
70    <para> Users not satisfied with the slot definition choice may opt
71    to replace the default slot function type with an alternative that
72    meets their specific needs.</para>
73  </section>
74
75  <section>
76    <title>User-level Connection Management</title>
77
78    <para> Users need to have fine control over the connection of
79    signals to slots and their eventual disconnection. The approach
80    taken by Boost.Signals is to return a
81    <code><classname>connection</classname></code> object that enables
82    connected/disconnected query, manual disconnection, and an
83    automatic disconnection on destruction mode. Some other possible
84    interfaces include:</para>
85
86    <itemizedlist>
87      <listitem>
88        <para><emphasis role="bold">Pass slot to
89        disconnect</emphasis>: in this interface model, the
90        disconnection of a slot connected with
91        <code>sig.<methodname>connect</methodname>(slot)</code> is
92        performed via
93        <code>sig.<methodname>disconnect</methodname>(slot)</code>. Internally,
94        a linear search using slot comparison is performed and the
95        slot, if found, is removed from the list. Unfortunately,
96        querying connectedness will generally also end up as
97        linear-time operations. This model also fails for
98        implementation reasons when slots become more complex than
99        simple function pointers, member function pointers and a
100        limited set of compositions and argument binders: to match the
101        slot given in the call to
102        <code><methodname>disconnect</methodname></code> with an
103        existing slot we would need to be able to compare arbitrary
104        function objects, which is not feasible.</para>
105      </listitem>
106     
107      <listitem>
108        <para><emphasis role="bold">Pass a token to
109        disconnect</emphasis>: this approach identifies slots with a
110        token that is easily comparable (e.g., a string), enabling
111        slots to be arbitrary function objects. While this approach is
112        essentially equivalent to the approach taken by Boost.Signals,
113        it is possibly more error-prone for several reasons:</para>
114
115        <itemizedlist>
116          <listitem>
117            <para>Connections and disconnections must be paired, so
118            the problem becomes similar to the problems incurred when
119            pairing <code>new</code> and <code>delete</code> for
120            dynamic memory allocation. While errors of this sort would
121            not be catastrophic for a signals and slots
122            implementation, their detection is generally
123            nontrivial.</para>
124          </listitem>
125         
126          <listitem>
127            <para>Tokens must be unique, otherwise two slots will have
128            the same name and will be indistinguishable. In
129            environments where many connections will be made
130            dynamically, name generation becomes an additional task
131            for the user. Uniqueness of tokens also results in an
132            additional failure mode when attempting to connect a slot
133            using a token that has already been used.</para>
134          </listitem>
135
136          <listitem>
137            <para>More parameterization would be required, because the
138            token type must be user-defined. Additional
139            parameterization steepens the learning curver and
140            overcomplicates a simple interface.</para>
141          </listitem>
142        </itemizedlist>
143
144        <para> This type of interface is supported in Boost.Signals
145        via the slot grouping mechanism. It augments the
146        <code><classname>connection</classname></code> object-based
147        connection management scheme.</para>
148      </listitem>
149    </itemizedlist>
150  </section>
151
152  <section>
153    <title>Combiner Interface</title>
154
155    <para> The Combiner interface was chosen to mimic a call to an
156    algorithm in the C++ standard library. It is felt that by viewing
157    slot call results as merely a sequence of values accessed by input
158    iterators, the combiner interface would be most natural to a
159    proficient C++ programmer. Competing interface design generally
160    required the combiners to be constructed to conform to an
161    interface that would be customized for (and limited to) the
162    Signals library. While these interfaces are generally enable more
163    straighforward implementation of the signals &amp; slots
164    libraries, the combiners are unfortunately not reusable (either in
165    other signals &amp; slots libraries or within other generic
166    algorithms), and the learning curve is steepened slightly to learn
167    the specific combiner interface.</para>
168
169    <para> The Signals formulation of combiners is based on the
170    combiner using the "pull" mode of communication, instead of the
171    more complex "push" mechanism. With a "pull" mechanism, the
172    combiner's state can be kept on the stack and in the program
173    counter, because whenever new data is required (i.e., calling the
174    next slot to retrieve its return value), there is a simple
175    interface to retrieve that data immediately and without returning
176    from the combiner's code. Contrast this with the "push" mechanism,
177    where the combiner must keep all state in class members because
178    the combiner's routines will be invoked for each signal
179    called. Compare, for example, a combiner that returns the maximum
180    element from calling the slots. If the maximum element ever
181    exceeds 100, no more slots are to be called.</para>
182
183    <informaltable>
184      <tgroup cols="2" align="left">
185        <thead>
186          <row>
187            <entry><para>Pull</para></entry>
188            <entry><para>Push</para></entry>
189          </row>
190        </thead>
191        <tbody>
192          <row>
193            <entry>
194<programlisting>
195struct pull_max {
196  typedef int result_type;
197
198  template&lt;typename InputIterator&gt;
199  result_type operator()(InputIterator first,
200                         InputIterator last)
201  {
202    if (first == last)
203      throw std::runtime_error("Empty!");
204
205    int max_value = *first++;
206    while(first != last &amp;&amp; *first &lt;= 100) {
207      if (*first &gt; max_value)
208        max_value = *first;
209      ++first;
210    }
211
212    return max_value;
213  }
214};
215</programlisting>
216</entry>
217            <entry>
218<programlisting>
219struct push_max {
220  typedef int result_type;
221
222  push_max() : max_value(), got_first(false) {}
223
224  // returns false when we want to stop
225  bool operator()(int result) {
226    if (result &gt; 100)
227      return false;
228
229    if (!got_first) {
230      got_first = true;
231      max_value = result;
232      return true;
233    }
234
235    if (result &gt; max_value)
236      max_value = result;
237
238    return true;
239  }
240
241  int get_value() const
242  {
243    if (!got_first)
244      throw std::runtime_error("Empty!");
245    return max_value;
246  }
247
248private:
249  int  max_value;
250  bool got_first;
251};
252</programlisting>
253</entry>
254          </row>
255        </tbody>
256      </tgroup>
257    </informaltable>
258
259    <para>There are several points to note in these examples. The
260    "pull" version is a reusable function object that is based on an
261    input iterator sequence with an integer <code>value_type</code>,
262    and is very straightforward in design. The "push" model, on the
263    other hand, relies on an interface specific to the caller and is
264    not generally reusable. It also requires extra state values to
265    determine, for instance, if any elements have been
266    received. Though code quality and ease-of-use is generally
267    subjective, the "pull" model is clearly shorter and more reusable
268    and will often be construed as easier to write and understand,
269    even outside the context of a signals &amp; slots library.</para>
270
271    <para> The cost of the "pull" combiner interface is paid in the
272    implementation of the Signals library itself. To correctly handle
273    slot disconnections during calls (e.g., when the dereference
274    operator is invoked), one must construct the iterator to skip over
275    disconnected slots. Additionally, the iterator must carry with it
276    the set of arguments to pass to each slot (although a reference to
277    a structure containing those arguments suffices), and must cache
278    the result of calling the slot so that multiple dereferences don't
279    result in multiple calls. This apparently requires a large degree
280    of overhead, though if one considers the entire process of
281    invoking slots one sees that the overhead is nearly equivalent to
282    that in the "push" model, but we have inverted the control
283    structures to make iteration and dereference complex (instead of
284    making combiner state-finding complex).</para>
285  </section>
286
287  <section>
288    <title>Connection Interfaces: +=  operator</title>
289
290    <para> Boost.Signals supports a connection syntax with the form
291    <code>sig.<methodname>connect</methodname>(slot)</code>, but a
292    more terse syntax <code>sig += slot</code> has been suggested (and
293    has been used by other signals &amp; slots implementations). There
294    are several reasons as to why this syntax has been
295    rejected:</para>
296
297    <itemizedlist>
298      <listitem>
299        <para><emphasis role="bold">It's unnecessary</emphasis>: the
300        connection syntax supplied by Boost.Signals is no less
301        powerful that that supplied by the <code>+=</code>
302        operator. The savings in typing (<code>connect()</code>
303        vs. <code>+=</code>) is essentially negligible. Furthermore,
304        one could argue that calling <code>connect()</code> is more
305        readable than an overload of <code>+=</code>.</para>
306      </listitem>
307      <listitem>
308        <para><emphasis role="bold">Ambiguous return type</emphasis>:
309        there is an ambiguity concerning the return value of the
310        <code>+=</code> operation: should it be a reference to the
311        signal itself, to enable <code>sig += slot1 += slot2</code>,
312        or should it return a
313        <code><classname>connection</classname></code> for the
314        newly-created signal/slot connection?</para>
315      </listitem>
316
317      <listitem>
318        <para><emphasis role="bold">Gateway to operators -=,
319        +</emphasis>: when one has added a connection operator
320        <code>+=</code>, it seems natural to have a disconnection
321        operator <code>-=</code>. However, this presents problems when
322        the library allows arbitrary function objects to implicitly
323        become slots, because slots are no longer comparable.  <!--
324        (see the discussion on this topic in User-level Connection
325        Management). --></para>
326
327        <para> The second obvious addition when one has
328        <code>operator+=</code> would be to add a <code>+</code>
329        operator that supports addition of multiple slots, followed by
330        assignment to a signal. However, this would require
331        implementing <code>+</code> such that it can accept any two
332        function objects, which is technically infeasible.</para>
333      </listitem>
334    </itemizedlist>
335  </section>
336   
337  <section>
338    <title><code>trackable</code> rationale</title>
339
340    <para> The <code><classname>trackable</classname></code>
341      class is the primary user interface to automatic connection
342      lifetime management, and its design affects users directly. Two
343      issues stick out most: the odd copying behavior of
344      <code>trackable</code>, and the limitation requiring users to
345      derive from <code>trackable</code> to create types that can
346      participate in automatic connection management.</para>
347
348    <section>
349      <title><code>trackable</code> copying behavior</title>
350
351      <para> The copying behavior of
352      <code><classname>trackable</classname></code> is essentially
353      that <code><classname>trackable</classname></code> subobjects
354      are never copied; instead, the copy operation is merely a
355      no-op. To understand this, we look at the nature of a
356      signal-slot connection and note that the connection is based on
357      the entities that are being connected; when one of the entities
358      is destroyed, the connection is destroyed. Therefore, when a
359      <code><classname>trackable</classname></code> subobject is
360      copied, we cannot copy the connections because the connections
361      don't refer to the target entity - they refer to the source
362      entity. This reason is dual to the reason signals are
363      noncopyable: the slots connected to them are connected to that
364      particular signal, not the data contained in the signal.</para>
365    </section>
366
367    <section>
368      <title>Why derivation from <code>trackable</code>?</title>
369
370      <para> For <code><classname>trackable</classname></code> to work
371      properly, there are two constraints:</para>
372
373      <itemizedlist>
374        <listitem>
375          <para><code><classname>trackable</classname></code> must
376          have storage space to keep track of all connections made to
377          this object.</para>
378        </listitem>
379
380        <listitem>
381          <para><code><classname>trackable</classname></code> must be
382          notified when the object is being destructed so that it can
383          disconnect its connections.</para>
384        </listitem>
385      </itemizedlist>
386
387      <para>Clearly, deriving from
388      <code><classname>trackable</classname></code> meets these two
389      guidelines. We have not yet found a superior solution.</para>
390    </section>
391  </section>
392
393  <section>
394    <title>Comparison with other Signal/Slot implementations</title>
395
396    <section>
397      <title>libsigc++</title>
398     
399      <para> <ulink
400      url="http://libsigc.sourceforge.net">libsigc++</ulink> is a C++
401      signals &amp; slots library that originally started as part of
402      an initiative to wrap the C interfaces to <ulink
403      url="http://www.gtk.org">GTK</ulink> libraries in C++, and has
404      grown to be a separate library maintained by Karl Nelson. There
405      are many similarities between libsigc++ and Boost.Signals, and
406      indeed Boost.Signals was strongly influenced by Karl Nelson and
407      libsigc++. A cursory inspection of each library will find a
408      similar syntax for the construction of signals and in the use of
409      connections and automatic connection lifetime management. There
410      are some major differences in design that separate these
411      libraries:</para>
412
413      <itemizedlist>
414        <listitem>
415          <para><emphasis role="bold">Slot definitions</emphasis>:
416          slots in libsigc++ are created using a set of primitives
417          defined by the library. These primitives allow binding of
418          objects (as part of the library), explicit adaptation from
419          the argument and return types of the signal to the argument
420          and return types of the slot (libsigc++ is, by default, more
421          strict about types than Boost.Signals). A discussion of this
422          approach with a comparison against the approach taken by
423          Boost.Signals is given in Choice of Slot Definitions.</para>
424        </listitem>
425
426        <listitem>
427          <para><emphasis role="bold">Combiner/Marshaller
428          interface</emphasis>: the equivalent to Boost.Signals
429          combiners in libsigc++ are the marshallers. Marshallers are
430          similar to the "push" interface described in Combiner
431          Interface, and a proper treatment of the topic is given
432          there.</para>
433        </listitem>
434      </itemizedlist>
435    </section>
436
437    <section>
438      <title>.NET delegates</title>
439
440      <para> <ulink url="http://www.microsoft.com">Microsoft</ulink>
441      has introduced the .NET Framework and an associated set of
442      languages and language extensions, one of which is the
443      delegate. Delegates are similar to signals and slots, but they
444      are more limited than most C++ signals and slots implementations
445      in that they:</para>
446
447      <itemizedlist>
448        <listitem>
449          <para>Require exact type matches between a delegate and what
450          it is calling.</para>
451        </listitem>
452
453        <listitem><para>Only return the result of the last target called, with no option for customization.</para></listitem>
454        <listitem>
455          <para>Must call a method with <code>this</code> already
456          bound.</para>
457        </listitem>
458      </itemizedlist>
459    </section>
460  </section>
461</section>
Note: See TracBrowser for help on using the repository browser.