I'm just a fledgling programmer that at least tries to program more than the best-case scenario. I've been reading Herb Sutter's "Exceptional C++" and went through the exception-safety chapters thrice so far. However, barring the example he posed (a Stack), I'm not really sure when exactly I should strive for exception safety vs speed and when it's just plain silly to do so.
For example, my current homework project is a doubly-linked list. Since I've programmed a couple of these already, I wanted to take the time to get into some deeper concepts such as ES.
Here is my pop-front function:
void List::pop_front()
{
if(!head_)
throw std::length_error("Pop front: List is empty.\n");
else
{
ListElem *temp = head_;
head_ = head_->next;
head_->prev = 0;
delete temp;
--size_;
}
}
I had some dilemmas with this.
1) Should I really throw an error when a list fails? Shouldn't I rather simply do nothing and return instead of forcing the user of the list to perform try {] catch() {} statements (that are also slow).
2) There are multiple error classes (plus the ListException my teacher demands we implement in the class). Is a custom error class really necessary for such a thing, and is there a general guide on when to use a specific exception class? (For example, range, length and boundary all sound alike)
3) I know I shouldn't change the program state until all that code that has thrown an exception be done. This is why I'm decrementing size_ last. Is this really necessary in this simple example? I know delete can't throw. Is it possible for head_->prev to ever throw when assigning to 0? (head is the first Node)
My push_back function:
void List::push_back(const T& data)
{
if(!tail_)
{
tail_ = new ListElem(data, 0, 0);
head_ = tail_;
}
else
{
tail_->next = new ListElem(data, 0, tail_);
tail_ = tail_->next;
}
++size_;
}
1) I hear often that anything can fail in a C++ program. Is it realistic to test if the constructor for ListElem fails (or tail_ during new
ing)?
2) Would it ever be necessary to test the type of data (currently a simple typedef int T
until I templatize everything) to make sure the type is viable for the structure?
I realize that these are overly simple examples, but I'm currently just confused as to when I should actually practice good ES and when it's not.
That's a long question. I'll take all the questions that are numbered
1)
.No. If your user cares about performance they will check the length before attempting to pop rather than popping and catching the exception. The exception is there to inform your user if they forget to check the length first, and at that point you really want the application to blow up in their face. If you just do nothing it could cause subtle problems which only show up later, and that will make debugging more difficult.
The constructor can for example fail if you run out of memory, but in this case it should throw an exception, not return null. So you shouldn't need to test explicitly for the constructor failing. See this question for more details: