Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A Base class technique for exception handling

Tags:

c++

The task of GotW#8 is to implement an exception-neutral generic stack data structure in C++, assuming only the template argument's destructor doesn't throw. The trick is to handle potentially throwing template argument operations (constructor, copy constructor, assignment) in a way to leave the stack in a consistent state if they throw.

In the solution, Herb Sutter says

To keep this solution simpler, I've decided not to demonstrate the base class technique for exception-safe resource ownership.

After some googling, I found this answer by Dave Abrahams dating back to 1997. In his solution he handles allocation and deletion of the memory in the base class, and implements the stack operations in the subclass. This way, he ensures that element copying in the copy constructor is separated from memory allocation, so that if the copying fails, the base class destructor is called, no matter what.

For reference, here is Dave's copy constructor with my comment added:

// v_ refers to the internal array storing the stack elements
Stack(const Stack& rhs)
        : StackBase<T>( rhs.Count() ) // constructor allocates enough space
                                      // destructor calls delete[] appropriately
{
        while ( Count() < rhs.Count() )
           Push( rhs.v_[ Count() ] ); // may throw
}

If the base constructor succeeds, the memory clean-up in base destructor is guaranteed even if the subclass's copy constructor throws.

My questions are:

  1. Is there any other benefit to the approach, except as outlined above?
  2. I came up with this copy-constructor when I solved the problem on my own:

    // v_ refers to the internal array storing the stack elements
    // vsize_ is the amount of space allocated in v_
    // vused_ is the amount of space used so far in v_
    Stack (const Stack &rhs) :
            vsize_ (0), vused_ (0), v_ (0) {
        Stack temp (rhs.vused_); // constructor calls `new T[num_elements]`
                                 // destructor calls `delete[] v_`
        std::copy (rhs.v_, rhs.v_ + rhs.vused_, temp.v_); // may throw
        swap (temp);
    }
    void swap (Stack &rhs) {
        std::swap (v_, rhs.v_);
        std::swap (vused_, rhs.vused_);
        std::swap (vsize_, rhs.vsize_);
    }
    

    I find it somewhat cumbersome to have a base class when compared to this approach. Is there any reason the base-class technique should be preferred over this temp-copy-then-swap approach? Note that both Dave and I already have the swap() member because we use it in our operator=().

  3. Dave Abrahams' technique doesn't seem to be very well known (according to Google). Does it have a different name, is it standard practice, have I missed something?

Notes:

  • assume Dave's Push() in a loop to be equivalent to my usage of std::copy
  • let's keep smart pointers out of the answer, as their usage would take away the point of managing memory explicitly in this exercise
like image 877
Irfy Avatar asked Mar 26 '13 18:03

Irfy


Video Answer


1 Answers

Behaviorally the two implementations are the same. They both setup a managed memory allocation object which will cleanup on scope exit if the constructor fails. Copying to a temp variable could be more expensive but, as noted in the comments, std::move would probably nullify such additional costs. In answer to your specific questions:

  1. The example by Abraham does move the heap allocation further away from your actual class implementation details. In your code, if you do more complicated memory manipulation before/after copying your array it could be slightly more difficult to ensure correct management of all entities. Otherwise I don't see any explicit details you didn't already cover beyond style in regards to the behavior of the first implementation.
  2. Abraham's implementation does abstract the cleanup to a single location. If multiple classes use StackBase<T> then they can each safely assume their dynamic memory will be cleaned up if they throw an exception. In your implementation you would need to rewrite (at least parts of) the temporary object and swap code to achieve the same. Effectively, this implementation reduces the number of lines to implement multiple subclasses of StackBase. However, your implementation avoids multiple inheritance if you wanted additional memory allocation on top of some other base class. Your code also avoids template code bloating compile time/sizes -- though I typically don't consider this to be a large negative in most cases regardless. I would probably use something close to your code as a default unless I was trying to write very general use case code.
  3. I don't know if this approach has a specific name -- I'll update this if I find one -- but I have seen it used in at least one C++ programming book before.
like image 180
Pyrce Avatar answered Oct 22 '22 02:10

Pyrce