Solution
Just as there's more than one way to skin a cat
(I have a feeling I'm going to get enraged e-mail from animal
lovers), there's more than one way to write exception-safe code. In
fact, there are two main alternatives we can choose from when it
comes to guaranteeing exception safety. These guarantees were first
set out in this form by Dave Abrahams.
-
Basic guarantee: Even in the presence of exceptions
thrown by T or other exceptions, Stack objects
don't leak resources. Note that this also implies that the
container will be destructible and usable even if an exception is
thrown while performing some container operation. However, if an
exception is thrown, the container will be in a consistent, but not
necessarily predictable, state. Containers that support the basic
guarantee can work safely in some settings.
-
Strong guarantee: If an operation terminates because
of an exception, program state will remain unchanged. This
always implies commit-or-rollback semantics, including that no
references or iterators into the container be invalidated if an
operation fails. For example, if a Stack client calls
Top and then attempts a Push that fails because
of an exception, then the state of the Stack object must
be unchanged and the reference returned from the prior call to
Top must still be valid. For more information on these
guarantees, see Dave Abrahams's documentation of the SGI
exception-safe standard library adaptation at: http://www.gotw.ca/publications/xc++/da_stlsafety.htm.
Probably the most interesting point here is that
when you implement the basic guarantee, the strong guarantee often
comes along for free. For example, in our Stack
implementation, almost everything we did was needed to satisfy just
the basic guarantee—and what's presented above very nearly
satisfies the strong guarantee, with little or no extra
work. Not half bad, considering all the trouble
we went to.
In addition to these two guarantees, there is
one more guarantee that certain functions must provide in order to
make overall exception safety possible:
-
Nothrow guarantee: The function will not emit an
exception under any circumstances. Overall exception safety
isn't possible unless certain functions are guaranteed not to
throw. In particular, we've seen that this is true for destructors;
later in this miniseries, we'll see that it's also needed in
certain helper functions, such as Swap().
Guideline
|
Understand the basic,
strong, and nothrow exception-safety guarantees.
|
Now we have some points to ponder. Note that
we've been able to implement Stack to be not only
exception-safe but fully exception-neutral, yet we've used only a
single try/catch. As we'll see next time, using
better encapsulation techniques can get rid of even this
try block. That means we can write a strongly
exception-safe and exception-neutral generic container, without
using try or catch—very natty, very elegant.
For the template as we've seen it so far,
Stack requires its instantiation type to have all of the
following:
-
Default constructor (to construct the
v_ buffers)
-
Copy constructor (if Pop returns by
value)
-
Nonthrowing destructor (to be able to guarantee
exception-safety)
-
Exception-safe
copy assignment (To set the values in v_, and if the copy
assignment throws, then it must guarantee that the target object is
still a valid T. Note that this is the only T
member function that must be exception-safe in order for our
Stack to be exception-safe.)
In the second half of this miniseries, we'll
also see how to reduce even these requirements, without
compromising exception safety. Along the way, we'll get an even
more-detailed look at the standard operation of the statement
delete[] x;.
|