]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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 | <!-- | |
5 | Copyright Douglas Gregor 2001-2004 | |
6 | Copyright Frank Mori Hess 2007-2009 | |
7 | ||
8 | Distributed under the Boost Software License, Version 1.0. (See accompanying | |
9 | file 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<void></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 & slots | |
150 | libraries, the combiners are unfortunately not reusable (either in | |
151 | other signals & 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> | |
181 | struct pull_max { | |
182 | typedef int result_type; | |
183 | ||
184 | template<typename InputIterator> | |
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 && *first <= 100) { | |
193 | if (*first > max_value) | |
194 | max_value = *first; | |
195 | ++first; | |
196 | } | |
197 | ||
198 | return max_value; | |
199 | } | |
200 | }; | |
201 | </programlisting> | |
202 | </entry> | |
203 | <entry> | |
204 | <programlisting> | |
205 | struct 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 > 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 > 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 | ||
234 | private: | |
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 & 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 & 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 & 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> |