in which he explores the problem of exception-safety for a generic stack
template. In his article, Cargill raises many useful questions, but
unfortunately fails to present a solution to his problem.<atitle=
"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."
<p>For many programmers more familiar with exceptions, the second example
is actually more readable and understandable than the first. The
``hidden'' code paths include the same calls to destructors of
local variables. In addition, they follow a simple pattern which acts
<i>exactly</i> as though there were a potential return statement after each
function call in case of an exception. Readability is enhanced because the
normal path of execution is unobscured by error-handling, and return values
are freed up to be used in a natural way.
<p>There is an even more important way in which exceptions can enhance
correctness: by allowing simple class invariants. In the first example, if
<code>x</code>'s constructor should need to allocate resources, it has no
way to report a failure: in C++, constructors have no return values. The
usual result when exceptions are avoided is that classes requiring
resources must include a separate initializer function which finishes the
job of construction. The programmer can therefore never be sure, when an
object of class <code>X</code> is used, whether he is handling a
full-fledged <code>X</code> or some abortive attempt to construct one (or
worse: someone simply forgot to call the initializer!)
<h2>3 A contractual basis for exception-safety</h2>
<p>A non-generic component can be described as exception-safe in isolation,
but because of its configurability by client code, exception-safety in a
generic component usually depends on a contract between the component and
its clients. For example, the designer of a generic component might require
that an operation which is used in the component's destructor not throw any
exceptions.<atitle=
" 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."
It is ``safe'' in the sense that it is not allowed to crash, but
its output may be unpredictable.
<p>The <i>strong</i> guarantee provides full
``commit-or-rollback'' semantics. In the case of C++ standard
containers, this means, for example, that if an exception is thrown all
iterators remain valid. We also know that the container has exactly the
same elements as before the exception was thrown. A transaction that has no
effects if it fails has obvious benefits: the program state is simple and
predictable in case of an exception. In the C++ standard library, nearly
all of the operations on the node-based containers list, set, multiset,
map, and multimap provide the <i>strong</i> guarantee.<atitle=
"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."
<p>The <i>no-throw</i> guarantee is the strongest of all, and it says that
an operation is guaranteed not to throw an exception: it always completes
successfully. This guarantee is necessary for most destructors, and indeed
the destructors of C++ standard library components are all guaranteed not
to throw exceptions. The <i>no-throw</i> guarantee turns out to be
important for other reasons, as we shall see.<atitle=
"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."
<p>Inevitably, the contract can get more complicated: a quid pro quo
arrangement is possible. Some components in the C++ Standard Library give
one guarantee for arbitrary type parameters, but give a stronger guarantee
in exchange for additional promises from the client type that no exceptions
will be thrown. For example, the standard container operation
<code>vector<T>::erase</code> gives the <i>basic</i> guarantee for
any <code>T</code>, but for types whose copy constructor and copy
assignment operator do not throw, it gives the <i>no-throw</i> guarantee.<a
title=
"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."
<p>This technique can be folded into a wrapper class to make a similar
container which provides stronger guarantees (and different performance
characteristics).<atitle=
"This suggests another potential use for the oft-wished-for but as yet unseen container traits<> template: automated container selection to meet exceptionsafety constraints."
set<T>::iterator i = set_impl.insert(t); // 4
try // 5
{ // 6
list_impl.push_back(i); // 7
} // 8
catch(...) // 9
{ // 10
set_impl.erase(i); // 11
throw; // 12
} // 13
} // 14
</pre>
</blockquote>
<p>What does our code actually require of the library? We need to examine
the lines where non-const operations occur:
<ul>
<li>Line 4: if the insertion fails but <code>set_impl</code> is modified
in the process, our invariant is violated. We need to be able to rely on
the <i>strong</i> guarantee from <code>set<T>::insert</code>.
<li>Line 7: likewise, if <code>push_back</code> fails, but
<code>list_impl</code> is modified in the process, our invariant is
violated, so we need to be able to rely on the <i>strong</i> guarantee
from list<T>::insert.
<li>Line 11: here we are ``rolling back'' the insertion on line
4. If this operation should fail, we will be unable to restore our
invariant. We absolutely depend on the <i>no-throw</i> guarantee from
<code>set<T>::erase</code>.<atitle=
"One might be tempted to surround the erase operation with a try/catch block to reduce the requirements on set<T> 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."
<li>Line 11: for the same reasons, we also depend on being able to pass
the <code>i</code> to the <code>erase</code> function: we need the
<i>no-throw</i> guarantee from the copy constructor of
<code>set<T>::iterator</code>.
</ul>
<p>I learned a great deal by approaching the question this way during
standardization. First, the guarantee specified for the composite container
actually depends on stronger guarantees from its components (the
<i>no-throw</i> guarantees in line 11). Also, I took advantage of all of
the natural level of safety to implement this simple example. Finally, the
analysis revealed a requirement on iterators which I had previously
overlooked when operations were considered on their own. The conclusion was
that we should provide as much of the natural level of safety as possible.
Faster but less-safe implementations could always be provided as extensions
to the standard components. <sup><atitle=
"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."
name="#footnote10">10</a></sup>
<h2>7 Automated testing for exception-safety</h2>
<p>As part of the standardization process, I produced an exception-safe
reference implementation of the STL. Error-handling code is seldom
rigorously tested in real life, in part because it is difficult to cause
error conditions to occur. It is very common to see error-handling code
which crashes the first time it is executed ...in a shipping product! To
bolster confidence that the implementation actually worked as advertised, I
designed an automated test suite, based on an exhaustive technique due to
my colleague Matt Arnold.
<p>The test program started with the basics: reinforcement and
instrumentation, especially of the global operators <code>new</code> and
<code>delete</code>.<sup><atitle=
"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."
name="#footnote11">11</a></sup>Instances of the components (containers and
algorithms) were created, with type parameters chosen to reveal as many
potential problems as possible. For example, all type parameters were given
a pointer to heap-allocated memory, so that leaking a contained object
would be detected as a memory leak.
<p>Finally, a scheme was designed that could cause an operation to throw an
exception at each possible point of failure. At the beginning of every
client-supplied operation which is allowed to throw an exception, a call to
<code>ThisCanThrow</code> was added. A call to <code>ThisCanThrow</code>
also had to be added everywhere that the generic operation being tested
might throw an exception, for example in the global operator
<code>new</code>, for which an instrumented replacement was supplied.
<p><code>ThisCanThrow</code> simply decrements a ``throw
counter'' and, if it has reached zero, throws an exception. Each test
takes a form which begins the counter at successively higher values in an
outer loop and repeatedly attempts to complete the operation being tested.
The result is that the operation throws an exception at each successive
step along its execution path that can possibly fail. For example, here is
a simplified version of the function used to test the <i>strong</i>
guarantee: <atitle=
"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."
is only slightly more formal, but relies on hard-to-read
``standardese'' and an occasionally subtle web of implication.<a
title=
"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."