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:
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=()
.
Notes:
Push()
in a loop to be equivalent to my usage of std::copy
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:
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.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With