Exceptions

From GPWiki
Jump to: navigation, search

Error handling is not the most entertaining part of programming, in fact it is often cumbersome (see this page). Some languages (C++, Python, Java) offer a mechanism that makes handling error conditions more convenient.

The idea behind exceptions is that exceptional conditions should only influence code at two points - the point where the problem occurs and the point that handles the problem. If these two points are the same piece of code, exceptions are not necessary, you can detect and solve things right there. But what if a function can cause a problem and a function higher up the calling hierarchy will handle the situation?

In a language that supports exception handling, whenever you encounter a situation that can not be solved right there right now, you 'throw' or 'raise' (the terms differ) an exception. This exception has certain properties that can be used to identify what kind of problem it indicates. After an exception has been thrown the stack starts to unwind (or a straight jump occurs, depending on the low-level properties of the language) until a piece of code is found that indicates it can handle (or 'catch') that kind of exception. In C++ this would look like this:

void Do_Dangerous_Stuff()
{
  Do_Safe_Stuff();
  bool all_safe = Are_Things_Okay();
  if (!all_safe)
    throw std::runtime_error("such and such problem occurred");
}
 
void Do_Something()
{
  Do_Dangerous_Stuff();
  Do_Something_Else();
}
 
int main()
{
  try{
    Do_Something();
  }
  catch(std::runtime_error& err){
    std::cout << "ERROR!! " << err.what() << '\n';
    exit(1); // or you could actually fix things
  }
}

The try/catch construction indicates "try to do this, but if it fails with such and such an exception, do this" (more than one 'catch' block can appear, catching different types of exceptions). Notice that the function Do_Something does not concern itself with exceptions, and does not have to.

Please note that, especially in C++, exceptions may make it easier to write error-handling, but they often make it harder to find the exact location where an error happened. Take this example:

try {
  char ch1 = new char[10];
  char ch2 = new char[10];
  char ch3 = new char[10];
} catch(std::bad_alloc &err) {
  // handle exception
}

In the catch block you now know that an allocation failed, but not which of the 3. In some cases this can make debugging harder. There is another problem in the above code: if an exception is thrown, the stack is automatically unwinded, but heap-objects are not destroyed. So if ch3 could not be allocated, both the memory of ch1 and ch2 are not freed and never can be, because the pointers don't exist anymore. Of course, in C++ you should just be using a std::vector instead of new[] anyways. If you make sure that all your resources are owned by objects, then those objects' destructors will automatically release them whether the function returns normally or exceptionally. This idiom is called RAII.