I l@ve RuBoard Previous Section Next Section

Solution

graphics/bulb_icon.gif

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.

  1. 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.

  2. 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.[7] 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.[8] Not half bad, considering all the trouble we went to.

    [7] Note that I said "often," not "always." In the standard library, for example, vector is a well-known counter-example in which satisfying the basic guarantee does not cause the strong guarantee to come along for free.

    [8] There is one subtle way in which this version of Stack still falls short of the strong guarantee. If Push() is called and has to grow its internal buffer, but then its final v_[vused_] = t; assignment throws, the Stack is still in a consistent state, but its internal memory buffer has moved梬hich invalidates any previously valid references returned from Top(). This last flaw in Stack::Push() can be fixed fairly easily by moving some code and adding a try block. For a better solution, however, see the Stack presented in the second half of this miniseries. That Stack does not have the problem, and it does satisfy the strong commit-or-rollback guarantee.

    In addition to these two guarantees, there is one more guarantee that certain functions must provide in order to make overall exception safety possible:

  3. 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

graphics/guideline_icon.gif

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;.

    I l@ve RuBoard Previous Section Next Section