rationale.xml 17 KB

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