I l@ve RuBoard Previous Section Next Section

Solution

graphics/bulb_icon.gif

To implement the copy constructor and the copy assignment operator, let's use a common helper function, NewCopy, to manage allocating and growing memory. NewCopy takes a pointer to (src) and size of (srcsize) an existing T buffer, and returns a pointer to a new and possibly larger copy of the buffer, passing ownership of the new buffer to the caller. If exceptions are encountered, NewCopy correctly releases all temporary resources and propagates the exception in such a way that nothing is leaked.



template<class T> 


T* NewCopy( const T* src,


            size_t   srcsize,


            size_t   destsize )


{


  assert( destsize >= srcsize );


  T* dest = new T[destsize];


  try


  {


    copy( src, src+srcsize, dest );


  }


  catch(...)


  {


    delete[] dest; // this can't throw


    throw;         // rethrow original exception


  }


  return dest;


}


Let's analyze this one step at a time.

  1. In the new statement, the allocation might throw bad_alloc or the T::T's may throw anything. In either case, nothing is allocated and we simply allow the exception to propagate. This is both leak-free and exception-neutral.

  2. Next, we assign all the existing values using T::operator=(). If any of the assignments fail, we catch the exception, free the allocated memory, and rethrow the original exception. This is again both leak-free and exception-neutral. However, there's an important subtlety here: T::operator=() must guarantee that if it does throw, then the assigned-to T object must be destructible.[4]

    [4] As we progress, we'll arrive at an improved version of Stack that does not rely on T::operator=.

  3. If the allocation and copy both succeed, then we return the pointer to the new buffer and relinquish ownership (that is, the caller is responsible for the buffer from here on out). The return simply copies the pointer value, which cannot throw.

Copy Construction

With NewCopy in hand, the Stack copy constructor is easy to write.



template<class T> 


Stack<T>::Stack( const Stack<T>& other )


  : v_(NewCopy( other.v_,


                other.vsize_,


                other.vsize_ )),


    vsize_(other.vsize_),


    vused_(other.vused_)


{


}


The only possible exception is from NewCopy, which manages its own resources.

Copy Assignment

Next, we tackle copy assignment.



template<class T> 


Stack<T>&


Stack<T>::operator=( const Stack<T>& other )


{


  if( this != &other )


  {


    T* v_new = NewCopy( other.v_,


                        other.vsize_,


                        other.vsize_ );


    delete[] v_;  // this can't throw


    v_ = v_new;   // take ownership


    vsize_ = other.vsize_;


    vused_ = other.vused_;


  }


  return *this;   // safe, no copy involved


}


Again, after the routine weak guard against self-assignment, only the NewCopy call might throw. If it does, we correctly propagate that exception, without affecting the Stack object's state. To the caller, if the assignment throws then the state is unchanged, and if the assignment doesn't throw, then the assignment and all its side effects are successful and complete.

What we see here is the following very important exception-safety idiom.

Guideline

graphics/guideline_icon.gif

Observe the canonical exception-safety rules: In each function, take all the code that might emit an exception and do all that work safely off to the side. Only then, when you know that the real work has succeeded, should you modify the program state (and clean up) using only nonthrowing operations.


    I l@ve RuBoard Previous Section Next Section