I l@ve RuBoard Previous Section Next Section

Item 12. Writing Exception-Safe Code桺art 5

Difficulty: 7

All right, you've had enough rest梤oll up your sleeves, and get ready for a wild ride.

Now we're ready to delve a little deeper into the same example, and write not just one but two new-and-improved versions of Stack. Not only is it, indeed, possible to write exception-safe generic containers, but by the time this miniseries is over, we'll have created no fewer than three complete solutions to the exception-safe Stack problem.

Along the way, we'll also discover the answers to several more interesting questions:

  • How can we use more-advanced techniques to simplify the way we manage resources and get rid of the last try/catch into the bargain?

  • How can we improve Stack by reducing the requirements on T, the contained type?

  • Should generic containers use exception specifications?

  • What do new[] and delete[] really do?

The answer to the last question may be quite different from what one might expect. Writing exception-safe containers in C++ isn't rocket science; it just requires significant care and a good understanding of how the language works. In particular, it helps to develop a habit of eyeing with mild suspicion anything that might turn out to be a function call梚ncluding user-defined operators, user-defined conversions, and silent temporary objects among the more subtle culprits梑ecause any function call might throw.[9]

[9] Except for functions declared with an exception specification of throw() or certain functions in the standard library that are documented to never throw.

One way to greatly simplify an exception-safe container like Stack is to use better encapsulation. Specifically, we'd like to encapsulate the basic memory management work. Most of the care we had to take while writing our original exception-safe Stack was needed just to get the basic memory allocation right, so let's introduce a simple helper class to put all that work in one place.



template <class T> class StackImpl 


{


/*????*/:


  StackImpl(size_t size=0);


  ~StackImpl();


  void Swap(StackImpl& other) throw();





  T*     v_;      // ptr to a memory area big


  size_t vsize_;  //  enough for 'vsize_' T's


  size_t vused_;  // # of T's actually in use





private:


  // private and undefined: no copying allowed


  StackImpl( const StackImpl& );


  StackImpl& operator=( const StackImpl& );


};


Note that StackImpl has all the original Stack's data members so that we've essentially moved the original Stack's representation entirely into StackImpl. StackImpl also has a helper function named Swap, which exchanges the guts of our StackImpl object with those of another StackImpl.

Your tasks:

  1. Implement all three member functions of StackImpl, but not just any old way. Assume that at any time, the v_ buffer must contain exactly as many constructed T objects as there are T objects in the container, no more, no less. In particular, unused space in the v_ buffer should not contain constructed T objects.

  2. Describe StackImpl's responsibilities. Why does it exist?

  3. What should /*????*/ be? How does the choice affect how StackImpl will be used? Be as specific as possible.

    I l@ve RuBoard Previous Section Next Section