generic_exception_safety.html 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
  2. <!-- saved from url=(0052)http://people.ne.mediaone.net/abrahams/abrahams.html -->
  3. <meta name="generator" content="HTML Tidy, see www.w3.org">
  4. <title>Exception-Safety in Generic Components</title>
  5. <meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
  6. <meta content="MSHTML 5.50.4522.1800" name="GENERATOR">
  7. <h1 align="center">Exception-Safety in Generic Components</h1>
  8. <p align="center"><i><b>Lessons Learned from Specifying Exception-Safety
  9. for the C++ Standard Library</b></i>
  10. <h3 align="center">David Abrahams</h3>
  11. <h3 align="center">Dragon Systems</h3>
  12. <h3 align="center"><code><a href=
  13. "mailto:David_Abrahams@dragonsys.com">David_Abrahams@dragonsys.com</a></code></h3>
  14. <p><b>Abstract.</b> This paper represents the knowledge accumulated in
  15. response to a real-world need: that the C++ Standard Template Library
  16. exhibit useful and well-defined interactions with exceptions, the
  17. error-handling mechanism built-in to the core C++ language. It explores the
  18. meaning of exception-safety, reveals surprising myths about exceptions and
  19. genericity, describes valuable tools for reasoning about program
  20. correctness, and outlines an automated testing procedure for verifying
  21. exception-safety.
  22. <p><b>Keywords:</b> exception-safety, exceptions, STL, C++
  23. <h2>1 What is exception-safety?</h2>
  24. <p>Informally, exception-safety in a component means that it exhibits
  25. reasonable behavior when an exception is thrown during its execution. For
  26. most people, the term ``reasonable'' includes all the usual
  27. expectations for error-handling: that resources should not be leaked, and
  28. that the program should remain in a well-defined state so that execution
  29. can continue. For most components, it also includes the expectation that
  30. when an error is encountered, it is reported to the caller.
  31. <p>More formally, we can describe a component as minimally exception-safe
  32. if, when exceptions are thrown from within that component, its invariants
  33. are intact. Later on we'll see that at least three different levels of
  34. exception-safety can be usefully distinguished. These distinctions can help
  35. us to describe and reason about the behavior of large systems.
  36. <p>In a generic component, we usually have an additional expectation of
  37. <i>exception-neutrality</i>, which means that exceptions thrown by a
  38. component's type parameters should be propagated, unchanged, to the
  39. component's caller.
  40. <h2>2 Myths and Superstitions</h2>
  41. <p>Exception-safety seems straightforward so far: it doesn't constitute
  42. anything more than we'd expect from code using more traditional
  43. error-handling techniques. It might be worthwhile, however, to examine the
  44. term from a psychological viewpoint. Nobody ever spoke of
  45. ``error-safety'' before C++ had exceptions.
  46. <p>It's almost as though exceptions are viewed as a <i>mysterious
  47. attack</i> on otherwise correct code, from which we must protect ourselves.
  48. Needless to say, this doesn't lead to a healthy relationship with error
  49. handling! During standardization, a democratic process which requires broad
  50. support for changes, I encountered many widely-held superstitions. In order
  51. to even begin the discussion of exception-safety in generic components, it
  52. may be worthwhile confronting a few of them.
  53. <p><i>``Interactions between templates and exceptions are not
  54. well-understood.''</i> This myth, often heard from those who consider
  55. these both new language features, is easily disposed of: there simply are
  56. no interactions. A template, once instantiated, works in all respects like
  57. an ordinary class or function. A simple way to reason about the behavior of
  58. a template with exceptions is to think of how a specific instantiation of
  59. that template works. Finally, the genericity of templates should not cause
  60. special concern. Although the component's client supplies part of the
  61. operation (which may, unless otherwise specified, throw arbitrary
  62. exceptions), the same is true of operations using familiar virtual
  63. functions or simple function pointers.
  64. <p><i>``It is well known to be impossible to write an exception-safe
  65. generic container.''</i> This claim is often heard with reference to
  66. an article by Tom Cargill <a title=
  67. "Tom Cargill, ``Exception Handling: A False Sense of Security'', C++ Report, Nov-Dec 1994"
  68. href=
  69. "http://people.ne.mediaone.net/abrahams/abrahams.html#reference4"><sup>[4]</sup></a>
  70. in which he explores the problem of exception-safety for a generic stack
  71. template. In his article, Cargill raises many useful questions, but
  72. unfortunately fails to present a solution to his problem.<a title=
  73. "Probably the greatest impediment to a solution in Cargill's case was an unfortunate combination of choices on his part: the interface he chose for his container was incompatible with his particular demands for safety. By changing either one he might have solved the problem."
  74. href=
  75. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote1"><sup>1</sup></a>
  76. He concludes by suggesting that a solution may not be possible.
  77. Unfortunately, his article was read by many as ``proof'' of that
  78. speculation. Since it was published there have been many examples of
  79. exception-safe generic components, among them the C++ standard library
  80. containers.
  81. <p><i>``Dealing with exceptions will slow code down, and templates are
  82. used specifically to get the best possible performance.''</i> A good
  83. implementation of C++ will not devote a single instruction cycle to dealing
  84. with exceptions until one is thrown, and then it can be handled at a speed
  85. comparable with that of calling a function <a title=
  86. "D. R. Musser, ``Introspective Sorting and Selection Algorithms'', Software-Practice and Experience 27(8):983-993, 1997."
  87. href=
  88. "http://people.ne.mediaone.net/abrahams/abrahams.html#reference7"><sup>[7]</sup></a>.
  89. That alone gives programs using exceptions performance equivalent to that
  90. of a program which ignores the possibility of errors. Using exceptions can
  91. actually result in faster programs than ``traditional'' error
  92. handling methods for other reasons. First, a catch block clearly indicates
  93. to the compiler which code is devoted to error-handling; it can then be
  94. separated from the usual execution path, improving locality of reference.
  95. Second, code using ``traditional'' error handling must typically
  96. test a return value for errors after every single function call; using
  97. exceptions completely eliminates that overhead.
  98. <p><i>``Exceptions make it more difficult to reason about a program's
  99. behavior.''</i> Usually cited in support of this myth is the way
  100. ``hidden'' execution paths are followed during stack-unwinding.
  101. Hidden execution paths are nothing new to any C++ programmer who expects
  102. local variables to be destroyed upon returning from a function:
  103. <blockquote>
  104. <pre>
  105. ErrorCode f( int&amp; result ) // 1
  106. { // 2
  107. X x; // 3
  108. ErrorCode err = x.g( result ); // 4
  109. if ( err != kNoError ) // 5
  110. return err; // 6
  111. // ...More code here...
  112. return kNoError; // 7
  113. }
  114. </pre>
  115. </blockquote>
  116. <p>In the example above, there is a ``hidden'' call to
  117. <code>X::~X()</code> in lines 6 and 7. Granted, using exceptions, there is
  118. no code devoted to error handling visible:
  119. <blockquote>
  120. <pre>
  121. int f() // 1
  122. { // 2
  123. X x; // 3
  124. int result = x.g(); // 4
  125. // ...More code here...
  126. return result; // 5
  127. }
  128. </pre>
  129. </blockquote>
  130. <p>For many programmers more familiar with exceptions, the second example
  131. is actually more readable and understandable than the first. The
  132. ``hidden'' code paths include the same calls to destructors of
  133. local variables. In addition, they follow a simple pattern which acts
  134. <i>exactly</i> as though there were a potential return statement after each
  135. function call in case of an exception. Readability is enhanced because the
  136. normal path of execution is unobscured by error-handling, and return values
  137. are freed up to be used in a natural way.
  138. <p>There is an even more important way in which exceptions can enhance
  139. correctness: by allowing simple class invariants. In the first example, if
  140. <code>x</code>'s constructor should need to allocate resources, it has no
  141. way to report a failure: in C++, constructors have no return values. The
  142. usual result when exceptions are avoided is that classes requiring
  143. resources must include a separate initializer function which finishes the
  144. job of construction. The programmer can therefore never be sure, when an
  145. object of class <code>X</code> is used, whether he is handling a
  146. full-fledged <code>X</code> or some abortive attempt to construct one (or
  147. worse: someone simply forgot to call the initializer!)
  148. <h2>3 A contractual basis for exception-safety</h2>
  149. <p>A non-generic component can be described as exception-safe in isolation,
  150. but because of its configurability by client code, exception-safety in a
  151. generic component usually depends on a contract between the component and
  152. its clients. For example, the designer of a generic component might require
  153. that an operation which is used in the component's destructor not throw any
  154. exceptions.<a title=
  155. " It is usually inadvisable to throw an exception from a destructor in C++, since the destructor may itself be called during the stack-unwinding caused by another exception. If the second exception is allowed to propagate beyond the destructor, the program is immediately terminated."
  156. href=
  157. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote2"><sup>2</sup></a>
  158. The generic component might, in return, provide one of the following
  159. guarantees:
  160. <ul>
  161. <li>The <i>basic</i> guarantee: that the invariants of the component are
  162. preserved, and no resources are leaked.
  163. <li>The <i>strong</i> guarantee: that the operation has either completed
  164. successfully or thrown an exception, leaving the program state exactly as
  165. it was before the operation started.
  166. <li>The <i>no-throw</i> guarantee: that the operation will not throw an
  167. exception.
  168. </ul>
  169. <p>The basic guarantee is a simple minimum standard for exception-safety to
  170. which we can hold all components. It says simply that after an exception,
  171. the component can still be used as before. Importantly, the preservation of
  172. invariants allows the component to be destroyed, potentially as part of
  173. stack-unwinding. This guarantee is actually less useful than it might at
  174. first appear. If a component has many valid states, after an exception we
  175. have no idea what state the component is in|only that the state is valid.
  176. The options for recovery in this case are limited: either destruction or
  177. resetting the component to some known state before further use. Consider
  178. the following example:
  179. <blockquote>
  180. <pre>
  181. template &lt;class X&gt;
  182. void print_random_sequence()
  183. {
  184. std::vector&lt;X&gt; v(10); // A vector of 10 items
  185. try {
  186. // Provides only the <i>basic</i> guarantee
  187. v.insert( v.begin(), X() );
  188. }
  189. catch(...) {} // ignore any exceptions above
  190. // print the vector's contents
  191. std::cout "(" &lt;&lt; v.size() &lt;&lt; ") ";
  192. std::copy( v.begin(), v.end(),
  193. std::ostream_iterator&lt;X&gt;( std::cout, " " ) );
  194. }
  195. </pre>
  196. </blockquote>
  197. <p>Since all we know about v after an exception is that it is valid, the
  198. function is allowed to print any random sequence of <code>X</code>s.<a
  199. title=
  200. "In practice of course, this function would make an extremely poor random sequence generator!"
  201. href=
  202. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote3"><sup>3</sup></a>
  203. It is ``safe'' in the sense that it is not allowed to crash, but
  204. its output may be unpredictable.
  205. <p>The <i>strong</i> guarantee provides full
  206. ``commit-or-rollback'' semantics. In the case of C++ standard
  207. containers, this means, for example, that if an exception is thrown all
  208. iterators remain valid. We also know that the container has exactly the
  209. same elements as before the exception was thrown. A transaction that has no
  210. effects if it fails has obvious benefits: the program state is simple and
  211. predictable in case of an exception. In the C++ standard library, nearly
  212. all of the operations on the node-based containers list, set, multiset,
  213. map, and multimap provide the <i>strong</i> guarantee.<a title=
  214. "It is worth noting that mutating algorithms usually cannot provide the strong guarantee: to roll back a modified element of a range, it must be set back to its previous value using operator=, which itself might throw. In the C++ standard library, there are a few exceptions to this rule, whose rollback behavior consists only of destruction: uninitialized_copy, uninitialized_fill, and uninitialized_fill_n."
  215. href=
  216. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote4"><sup>4</sup></a>).
  217. <p>The <i>no-throw</i> guarantee is the strongest of all, and it says that
  218. an operation is guaranteed not to throw an exception: it always completes
  219. successfully. This guarantee is necessary for most destructors, and indeed
  220. the destructors of C++ standard library components are all guaranteed not
  221. to throw exceptions. The <i>no-throw</i> guarantee turns out to be
  222. important for other reasons, as we shall see.<a title=
  223. "All type parameters supplied by clients of the C++ standard library are required not to throw from their destructors. In return, all components of the C++ standard library provide at least the basic guarantee."
  224. href=
  225. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote5"><sup>5</sup></a>
  226. <h2>4 Legal Wrangling</h2>
  227. <p>Inevitably, the contract can get more complicated: a quid pro quo
  228. arrangement is possible. Some components in the C++ Standard Library give
  229. one guarantee for arbitrary type parameters, but give a stronger guarantee
  230. in exchange for additional promises from the client type that no exceptions
  231. will be thrown. For example, the standard container operation
  232. <code>vector&lt;T&gt;::erase</code> gives the <i>basic</i> guarantee for
  233. any <code>T</code>, but for types whose copy constructor and copy
  234. assignment operator do not throw, it gives the <i>no-throw</i> guarantee.<a
  235. title=
  236. "Similar arrangements might have been made in the C++ standard for many of the mutating algorithms, but were never considered due to time constraints on the standardization process."
  237. href=
  238. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote6"><sup>6</sup></a>
  239. <h2>5 What level of exception-safety should a component specify?</h2>
  240. <p>From a client's point-of-view, the strongest possible level of safety
  241. would be ideal. Of course, the <i>no-throw</i> guarantee is simply
  242. impossible for many operations, but what about the <i>strong</i> guarantee?
  243. For example, suppose we wanted atomic behavior for
  244. <code>vector&lt;T&gt;::insert</code>. Insertion into the middle of a vector
  245. requires copying elements after the insertion point into later positions,
  246. to make room for the new element. If copying an element can fail, rolling
  247. back the operation would require ``undoing'' the previous
  248. copies...which depends on copying again. If copying back should fail (as it
  249. likely would), we have failed to meet our guarantee.
  250. <p>One possible alternative would be to redefine <code>insert</code> to
  251. build the new array contents in a fresh piece of memory each time, and only
  252. destroy the old contents when that has succeeded. Unfortunately, there is a
  253. non-trivial cost if this approach is followed: insertions near the end of a
  254. vector which might have previously caused only a few copies would now cause
  255. every element to be copied. The <i>basic</i> guarantee is a
  256. ``natural'' level of safety for this operation, which it can
  257. provide without violating its performance guarantees. In fact all of the
  258. operations in the library appear to have such a ``natural'' level
  259. of safety.
  260. <p>Because performance requirements were already a well-established part of
  261. the draft standard and because performance is a primary goal of the STL,
  262. there was no attempt to specify more safety than could be provided within
  263. those requirements. Although not all of the library gives the <i>strong</i>
  264. guarantee, almost any operation on a standard container which gives the
  265. <i>basic</i> guarantee can be made <i>strong</i> using the ``make a
  266. new copy'' strategy described above:
  267. <blockquote>
  268. <pre>
  269. template &lt;class Container, class BasicOp&gt;
  270. void MakeOperationStrong( Container&amp; c, const BasicOp&amp; op )
  271. {
  272. Container tmp(c); // Copy c
  273. op(tmp); // Work on the copy
  274. c.swap(tmp); // Cannot fail<a title=
  275. "Associative containers whose Compare object might throw an exception when copied cannot use this technique, since the swap function might fail."
  276. href=
  277. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote7"><sup>7</sup></a>
  278. }
  279. </pre>
  280. </blockquote>
  281. <p>This technique can be folded into a wrapper class to make a similar
  282. container which provides stronger guarantees (and different performance
  283. characteristics).<a title=
  284. "This suggests another potential use for the oft-wished-for but as yet unseen container traits&lt;&gt; template: automated container selection to meet exceptionsafety constraints."
  285. href=
  286. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote8"><sup>8</sup></a>
  287. <h2>6 Should we take everything we can get?</h2>
  288. <p>By considering a particular implementation, we can hope to discern a
  289. natural level of safety. The danger in using this to establish requirements
  290. for a component is that the implementation might be restricted. If someone
  291. should come up with a more-efficient implementation which we'd like to use,
  292. we may find that it's incompatible with our exception-safety requirements.
  293. One might expect this to be of no concern in the well-explored domains of
  294. data structures and algorithms covered by the STL, but even there, advances
  295. are being made. A good example is the recent <i>introsort</i> algorithm <a
  296. title=
  297. "D. R. Musser, ``Introspective Sorting and Selection Algorithms'', Software-Practice and Experience 27(8):983-993, 1997."
  298. href=
  299. "http://people.ne.mediaone.net/abrahams/abrahams.html#reference6"><sup>[6]</sup></a>,
  300. which represents a substantial improvement in worst-case complexity over
  301. the well-established <i>quicksort</i>.
  302. <p>To determine exactly how much to demand of the standard components, I
  303. looked at a typical real-world scenario. The chosen test case was a
  304. ``composite container.'' Such a container, built of two or more
  305. standard container components, is not only commonly needed, but serves as a
  306. simple representative case for maintaining invariants in larger systems:
  307. <blockquote>
  308. <pre>
  309. // SearchableStack - A stack which can be efficiently searched
  310. // for any value.
  311. template &lt;class T&gt;
  312. class SearchableStack
  313. {
  314. public:
  315. void push(const T&amp; t); // O(log n)
  316. void pop(); // O(log n)
  317. bool contains(const T&amp; t) const; // O(log n)
  318. const T&amp; top() const; // O(1)
  319. private:
  320. std::set&lt;T&gt; set_impl;
  321. std::list&lt;std::set&lt;T&gt;::iterator&gt; list_impl;
  322. };
  323. </pre>
  324. </blockquote>
  325. <p>The idea is that the list acts as a stack of set iterators: every
  326. element goes into the set first, and the resulting position is pushed onto
  327. the list. The invariant is straightforward: the set and the list should
  328. always have the same number of elements, and every element of the set
  329. should be referenced by an element of the list. The following
  330. implementation of the push function is designed to give the <i>strong</i>
  331. guarantee within the natural levels of safety provided by set and list:
  332. <blockquote>
  333. <pre>
  334. template &lt;class T&gt; // 1
  335. void SearchableStack&lt;T&gt;::push(const T&amp; t) // 2
  336. { // 3
  337. set&lt;T&gt;::iterator i = set_impl.insert(t); // 4
  338. try // 5
  339. { // 6
  340. list_impl.push_back(i); // 7
  341. } // 8
  342. catch(...) // 9
  343. { // 10
  344. set_impl.erase(i); // 11
  345. throw; // 12
  346. } // 13
  347. } // 14
  348. </pre>
  349. </blockquote>
  350. <p>What does our code actually require of the library? We need to examine
  351. the lines where non-const operations occur:
  352. <ul>
  353. <li>Line 4: if the insertion fails but <code>set_impl</code> is modified
  354. in the process, our invariant is violated. We need to be able to rely on
  355. the <i>strong</i> guarantee from <code>set&lt;T&gt;::insert</code>.
  356. <li>Line 7: likewise, if <code>push_back</code> fails, but
  357. <code>list_impl</code> is modified in the process, our invariant is
  358. violated, so we need to be able to rely on the <i>strong</i> guarantee
  359. from list&lt;T&gt;::insert.
  360. <li>Line 11: here we are ``rolling back'' the insertion on line
  361. 4. If this operation should fail, we will be unable to restore our
  362. invariant. We absolutely depend on the <i>no-throw</i> guarantee from
  363. <code>set&lt;T&gt;::erase</code>.<a title=
  364. "One might be tempted to surround the erase operation with a try/catch block to reduce the requirements on set&lt;T&gt; and the problems that arise in case of an exception, but in the end that just begs the question. First, erase just failed and in this case there are no viable alternative ways to produce the necessary result. Second and more generally, because of the variability of its type parameters a generic component can seldom be assured that any alternatives will succeed."
  365. href=
  366. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote9"><sup>9</sup></a>
  367. <li>Line 11: for the same reasons, we also depend on being able to pass
  368. the <code>i</code> to the <code>erase</code> function: we need the
  369. <i>no-throw</i> guarantee from the copy constructor of
  370. <code>set&lt;T&gt;::iterator</code>.
  371. </ul>
  372. <p>I learned a great deal by approaching the question this way during
  373. standardization. First, the guarantee specified for the composite container
  374. actually depends on stronger guarantees from its components (the
  375. <i>no-throw</i> guarantees in line 11). Also, I took advantage of all of
  376. the natural level of safety to implement this simple example. Finally, the
  377. analysis revealed a requirement on iterators which I had previously
  378. overlooked when operations were considered on their own. The conclusion was
  379. that we should provide as much of the natural level of safety as possible.
  380. Faster but less-safe implementations could always be provided as extensions
  381. to the standard components. <sup><a title=
  382. "The prevalent philosophy in the design of STL was that functionality that wasn't essential to all uses should be left out in favor of efficiency, as long as that functionality could be obtained when needed by adapting the base components. This departs from that philosophy, but it would be difficult or impossible to obtain even the basic guarantee by adapting a base component that doesn't already have it."
  383. name="#footnote10">10</a></sup>
  384. <h2>7 Automated testing for exception-safety</h2>
  385. <p>As part of the standardization process, I produced an exception-safe
  386. reference implementation of the STL. Error-handling code is seldom
  387. rigorously tested in real life, in part because it is difficult to cause
  388. error conditions to occur. It is very common to see error-handling code
  389. which crashes the first time it is executed ...in a shipping product! To
  390. bolster confidence that the implementation actually worked as advertised, I
  391. designed an automated test suite, based on an exhaustive technique due to
  392. my colleague Matt Arnold.
  393. <p>The test program started with the basics: reinforcement and
  394. instrumentation, especially of the global operators <code>new</code> and
  395. <code>delete</code>.<sup><a title=
  396. "An excellent discussion on how to fortify memory subsystems can be found in: Steve Maguire, Writing Solid Code, Microsoft Press, Redmond, WA, 1993, ISBN 1-55615- 551-4."
  397. name="#footnote11">11</a></sup>Instances of the components (containers and
  398. algorithms) were created, with type parameters chosen to reveal as many
  399. potential problems as possible. For example, all type parameters were given
  400. a pointer to heap-allocated memory, so that leaking a contained object
  401. would be detected as a memory leak.
  402. <p>Finally, a scheme was designed that could cause an operation to throw an
  403. exception at each possible point of failure. At the beginning of every
  404. client-supplied operation which is allowed to throw an exception, a call to
  405. <code>ThisCanThrow</code> was added. A call to <code>ThisCanThrow</code>
  406. also had to be added everywhere that the generic operation being tested
  407. might throw an exception, for example in the global operator
  408. <code>new</code>, for which an instrumented replacement was supplied.
  409. <blockquote>
  410. <pre>
  411. // Use this as a type parameter, e.g. vector&lt;TestClass&gt;
  412. struct TestClass
  413. {
  414. TestClass( int v = 0 )
  415. : p( ThisCanThrow(), new int( v ) ) {}
  416. TestClass( const TestClass&amp; rhs )
  417. : p( ThisCanThrow(), new int( *rhs.p ) ) {}
  418. const TestClass&amp; operator=( const TestClass&amp; rhs )
  419. { ThisCanThrow(); *p = *rhs.p; }
  420. bool operator==( const TestClass&amp; rhs )
  421. { ThisCanThrow(); return *p == *rhs.p; }
  422. ...etc...
  423. ~TestClass() { delete p; }
  424. };
  425. </pre>
  426. </blockquote>
  427. <p><code>ThisCanThrow</code> simply decrements a ``throw
  428. counter'' and, if it has reached zero, throws an exception. Each test
  429. takes a form which begins the counter at successively higher values in an
  430. outer loop and repeatedly attempts to complete the operation being tested.
  431. The result is that the operation throws an exception at each successive
  432. step along its execution path that can possibly fail. For example, here is
  433. a simplified version of the function used to test the <i>strong</i>
  434. guarantee: <a title=
  435. "Note that this technique requires that the operation being tested be exception-neutral. If the operation ever tries to recover from an exception and proceed, the throw counter will be negative, and subsequent operations that might fail will not be tested for exception-safety."
  436. href=
  437. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote12"><sup>12</sup></a>
  438. <blockquote>
  439. <pre>
  440. extern int gThrowCounter; // The throw counter
  441. void ThisCanThrow()
  442. {
  443. if (gThrowCounter-- == 0)
  444. throw 0;
  445. }
  446. template &lt;class Value, class Operation&gt;
  447. void StrongCheck(const Value&amp; v, const Operation&amp; op)
  448. {
  449. bool succeeded = false;
  450. for (long nextThrowCount = 0; !succeeded; ++nextThrowCount)
  451. {
  452. Value duplicate = v;
  453. try
  454. {
  455. gThrowCounter = nextThrowCount;
  456. op( duplicate ); // Try the operation
  457. succeeded = true;
  458. }
  459. catch(...) // Catch all exceptions
  460. {
  461. bool unchanged = duplicate == v; // Test <i>strong</i> guarantee
  462. assert( unchanged );
  463. }
  464. // Specialize as desired for each container type, to check
  465. // integrity. For example, size() == distance(begin(),end())
  466. CheckInvariant(v); // Check any invariant
  467. }
  468. }
  469. </pre>
  470. </blockquote>
  471. <p>Notably, this kind of testing is much easier and less intrusive with a
  472. generic component than with non-generics, because testing-specific type
  473. parameters can be used without modifying the source code of the component
  474. being tested. Also, generic functions like <code>StrongCheck</code> above
  475. were instrumental in performing the tests on a wide range of values and
  476. operations.
  477. <h2>8 Further Reading</h2>
  478. To my knowledge, there are currently only two descriptions of STL
  479. exception-safety available. The original specification <a title=
  480. "D. Abrahams, Exception Safety in STLport" href=
  481. "http://people.ne.mediaone.net/abrahams/abrahams.html#reference2"><sup>[2]</sup></a>
  482. for the reference exception-safe implementation of the STL is an informal
  483. specification, simple and self-explanatory (also verbose), and uses the
  484. <i>basic-</i> and <i>strong-</i>guarantee distinctions outlined in this
  485. article. It explicitly forbids leaks, and differs substantively from the
  486. final C++ standard in the guarantees it makes, though they are largely
  487. identical. I hope to produce an updated version of this document soon.
  488. <p>The description of exception-safety in the C++ Standard <a title=
  489. "International Standard ISO/IEC 14882, Information Technology-Programming Languages-C++, Document Number ISO/IEC 14882-1998"
  490. href=
  491. "http://people.ne.mediaone.net/abrahams/abrahams.html#reference1"><sup>[1]</sup></a>
  492. is only slightly more formal, but relies on hard-to-read
  493. ``standardese'' and an occasionally subtle web of implication.<a
  494. title=
  495. "The changes to the draft standard which introduced exception-safety were made late in the process, when amendments were likely to be rejected solely on the basis of the number of altered words. Unfortunately, the result compromises clarity somewhat in favor of brevity. Greg Colvin was responsible for the clever language-lawyering needed to minimize the extent of these changes."
  496. href=
  497. "http://people.ne.mediaone.net/abrahams/abrahams.html#footnote13"><sup>13</sup></a>
  498. In particular, leaks are not treated directly at all. It does have the
  499. advantage that it <i>is</i> the standard.
  500. <p>The original reference implementation <a title=
  501. "B. Fomitchev, Adapted SGI STL Version 1.0, with exception handling code by D. Abrahams"
  502. href=
  503. "http://people.ne.mediaone.net/abrahams/abrahams.html#reference5"><sup>[5]</sup></a>
  504. of the exception-safe STL is an adaptation of an old version of the SGI
  505. STL, designed for C++ compilers with limited features. Although it is not a
  506. complete STL implementation, the code may be easier to read, and it
  507. illustrates a useful base-class technique for eliminating
  508. exception-handling code in constructors. The full test suite <a title=
  509. "D. Abrahams and B. Fomitchev, Exception Handling Test Suite" href=
  510. "http://people.ne.mediaone.net/abrahams/abrahams.html#reference3"><sup>[3]</sup></a>
  511. used to validate the reference implementation has been used successfully to
  512. validate all recent versions of the SGI STL, and has been adapted to test
  513. one other vendor's implementation (which failed). As noted on the
  514. documentation page, it also seems to have the power to reveal hidden
  515. compiler bugs, particularly where optimizers interact with
  516. exception-handling code.
  517. <h2>References</h2>
  518. <ol>
  519. <li><a name="reference1">International</a> Standard ISO/IEC 14882,
  520. <i>Information Technology-Programming Languages-C++</i>, Document Number
  521. ISO/IEC 14882-1998, available from <a href=
  522. "http://webstore.ansi.org/ansidocstore/default.asp">http://webstore.ansi.org/ansidocstore/default.asp</a>.
  523. <li><a name="reference2">D.</a> Abrahams, <i>Exception Safety in
  524. STLport</i>, available at <a href=
  525. "http://www.stlport.org/doc/exception_safety.html">http://www.stlport.org/doc/exception_safety.html</a>.
  526. <li><a name="reference3">D.</a> Abrahams and B. Fomitchev, <i>Exception
  527. Handling Test Suite</i>, available at <a href=
  528. "http://www.stlport.org/doc/eh_testsuite.html">http://www.stlport.org/doc/eh_testsuite.html</a>.
  529. <li><a name="reference4">Tom</a> Cargill, ``Exception Handling: A
  530. False Sense of Security,'' C++ Report, Nov-Dec 1994, also available
  531. at <a href=
  532. "http://www.awl.com/cp/mec++-cargill.html">http://www.awl.com/cp/mec++-cargill.html</a>.
  533. <li><a name="reference5">B.</a> Fomitchev, <i>Adapted SGI STL Version
  534. 1.0</i>, with exception handling code by D. Abrahams, available at <a
  535. href=
  536. "http://www.metabyte.com/~fbp/stl/old.html">http://www.metabyte.com/~fbp/stl/old.html</a>.
  537. <li><a name="reference6">D.</a> R. Musser, ``Introspective Sorting
  538. and Selection Algorithms,'' <i>Software-Practice and Experience</i>
  539. 27(8):983-993, 1997.
  540. <li><a name="reference7">Bjarne</a> Stroustrup, <i>The Design And
  541. Evolution of C++</i>. Addison Wesley, Reading, MA, 1995, ISBN
  542. 0-201-54330-3, Section 16.9.1.
  543. </ol>
  544. <h2>Footnotes</h2>
  545. <p><a name="footnote1">1</a> Probably the greatest impediment to a solution
  546. in Cargill's case was an unfortunate combination of choices on his part:
  547. the interface he chose for his container was incompatible with his
  548. particular demands for safety. By changing either one he might have solved
  549. the problem.
  550. <p><a name="footnote2">2</a> It is usually inadvisable to throw an
  551. exception from a destructor in C++, since the destructor may itself be
  552. called during the stack-unwinding caused by another exception. If the
  553. second exception is allowed to propagate beyond the destructor, the program
  554. is immediately terminated.
  555. <p><a name="footnote3">3</a> In practice of course, this function would
  556. make an extremely poor random sequence generator!
  557. <p><a name="footnote4">4</a> It is worth noting that mutating algorithms
  558. usually cannot provide the <i>strong</i> guarantee: to roll back a modified
  559. element of a range, it must be set back to its previous value using
  560. <code>operator=</code>, which itself might throw. In the C++ standard
  561. library, there are a few exceptions to this rule, whose rollback behavior
  562. consists only of destruction: <code>uninitialized_copy</code>,
  563. <code>uninitialized_fill</code>, and <code>uninitialized_fill_n</code>.
  564. <p><a name="footnote5">5</a> All type parameters supplied by clients of the
  565. C++ standard library are required not to throw from their destructors. In
  566. return, all components of the C++ standard library provide at least the
  567. <i>basic</i> guarantee.
  568. <p><a name="footnote6">6</a> Similar arrangements might have been made in
  569. the C++ standard for many of the mutating algorithms, but were never
  570. considered due to time constraints on the standardization process.
  571. <p><a name="footnote7">7</a> Associative containers whose
  572. <code>Compare</code> object might throw an exception when copied cannot use
  573. this technique, since the swap function might fail.
  574. <p><a name="footnote8">8</a> This suggests another potential use for the
  575. oft-wished-for but as yet unseen <code>container_traits&lt;&gt;</code>
  576. template: automated container selection to meet exception-safety
  577. constraints.
  578. <p><a name="footnote9">9</a> One might be tempted to surround the erase
  579. operation with a <code>try</code>/<code>catch</code> block to reduce the
  580. requirements on <code>set&lt;T&gt;</code> and the problems that arise in
  581. case of an exception, but in the end that just begs the question. First,
  582. erase just failed and in this case there are no viable alternative ways to
  583. produce the necessary result. Second and more generally, because of the
  584. variability of its type parameters a generic component can seldom be
  585. assured that any alternatives will succeed.
  586. <p><a name="footnote10">10</a> The prevalent philosophy in the design of
  587. STL was that functionality that wasn't essential to all uses should be left
  588. out in favor of efficiency, as long as that functionality could be obtained
  589. when needed by adapting the base components. This departs from that
  590. philosophy, but it would be difficult or impossible to obtain even the
  591. <i>basic</i> guarantee by adapting a base component that doesn't already
  592. have it.
  593. <p><a name="footnote11">11</a> An excellent discussion on how to fortify
  594. memory subsystems can be found in: Steve Maguire, Writing Solid Code,
  595. Microsoft Press, Redmond, WA, 1993, ISBN 1-55615- 551-4.
  596. <p><a name="footnote12">12</a> Note that this technique requires that the
  597. operation being tested be exception-neutral. If the operation ever tries to
  598. recover from an exception and proceed, the throw counter will be negative,
  599. and subsequent operations that might fail will not be tested for
  600. exception-safety.
  601. <p><a name="footnote13">13</a> The changes to the draft standard which
  602. introduced exception-safety were made late in the process, when amendments
  603. were likely to be rejected solely on the basis of the number of altered
  604. words. Unfortunately, the result compromises clarity somewhat in favor of
  605. brevity. Greg Colvin was responsible for the clever language-lawyering
  606. needed to minimize the extent of these changes.
粤ICP备19079148号