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梐nd 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梫ery 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;.
|