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<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> 181struct 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> 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 > 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 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 & 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> 406