Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
87 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

  Contents

 Overview
 Implications
 Specifications
 Designing With
 Exceptions

 Resources
 Interactions With
 Threads

 Real-Time Systems
 Philosophies
 Potential Pitfalls

 Printable version

 


Exception Handling Philosophies

The fundamental reason for throwing an exception is to stop program execution along the current path. This generally means an error condition has occurred and normal program flow cannot continue. The best benefit to this is that client code can be simplified and yet still allows the implementer of a component to guarantee that if an error occurs the client will be aware of it. Basically, it removes the responsibility of a client to check error codes.

There seem to be three common reasons for throwing an exception: 

  1. A programming error - a pointer is null when it should not be
  2. A resource is not available for acquisition or release - bad_alloc on memory exhaustion
  3. A violation of a class invariant or a state of an object invariant - such as an invalid parameter is passed to a method or a calculation that goes out of range

So the reasons to throw an exception seem pretty straightforward. The more difficult question is where to handle exceptions. This is can be a lot more complicated an issue because of the flow control aspect of exceptions. We have to look at what most try/catch blocks are trying to accomplish.

Generally, I've seen try/catch blocks trying to do a few different things:

  1. Prevent resource leaks
  2. Stop an operation after it has failed 
  3. Stop an operation deciding how to continue based on the cause of the error

As we've seen from "Resource allocation is initialization" the first use of a try/catch block is completely unnecessary. We can eliminate the need for the try block, improving the clarity of the code, by using auto_ptr and similar classes to handle the releasing of resources in their destructors. This can also work for thread synchronization objects ( at least ones which you are not going to timeout on, such as a critical section ) and can be applied to other forms of resource acquisition. So, we can easily, relatively speaking, remove the try/catch blocks which are only preventing resource leaks and improve program performance and clarity.

The second use of a try/catch block is probably the most coherent use of exception handling. At some level in the system we attempt to perform a, usually, complex task which may fail in a number of ways. In this use of a try/catch block we are only concerned with complete success. No matter what the failure is we handle it in the exact same manner. Informing the user of the cause of the error can still be handled in a generic way, particularly if you have your own exception hierarchy. Your base class of your exceptions can provide a method for translating an exception into a human readable message that can be presented to the user. This allows the user to know why the operation failed and yet keeps the flow control simple in the error handler case.

The last example of exception handling seems at first like a simple permutation on the second one, but there are number of consequences involved. Having more than one catch block amounts is a form of branching and introduces more complex flow control. There are instances where this is unavoidable but hopefully you can minimize them. Maybe you handle only a few specific exceptions and the others are not handled. Perhaps these are from an unrelated exception hierarchy or you must handle specific exception classes in unique ways.

The critical question to consider is whether or not the system is handling the exception at the given location. Handling the exception can usually be read as not re-throwing it. If you are re-throwing an exception then you're not really handling it, you're just trying to return your object or system to a valid state. And if you find yourself having to do this in a number of different ways depending upon the exception thrown then you are potentially creating future maintenance problems. The reason for this is that it implies that proper error handling follows a chain of exception handlers that are spread across different levels of the system. 

To minimize this complexity you should let the exceptions go up to the highest level possible in the system, usually the point at which the application, or highest level components of the package, initiated the operation.

Similarly, a thrown exception should always propagate outside of the function which generated the exception. They should also usually propagate out of the component which generated them. This follows the idea that exceptions are a non local form of error handling. If you throw an exception in within a function and handle it directly in that function then it is being used as a form of flow control and can obscure that flow.





Next : Potential Pitfalls