Exception Handling
Exceptions happen all the time; for example, if you attempt to open a nonexistent file or you try to connect to a remote database engine when the intervening network is down. Given the increasingly distributed nature of computing, it’s essential to handle exceptions gracefully. As we’ll see, adding exception handling to your function calls can help make code far more resilient. It’s pretty easy to do, and the users of your function calls will thank you for it!
Exceptions: try-throw-catch
Listing 10 illustrates the basic exception handler structure.
Listing 10 Generic C++ Exception Handler
try { Some statements Some code with a throw statement More statements } catch (Exception_Type_Name e) { Statements performed if an instance of the specified exception is thrown }
Let’s take a look at a concrete example of an exception. To make it interesting, let’s throw an object of a class in Listing 11!
Listing 11 An Exception Handler—Throwing an Object of a Class
class AnExceptionalClass { public: explicit AnExceptionalClass(); explicit AnExceptionalClass(int someErrorCount); const int getErrorCount(); ~AnExceptionalClass() { }; private: int ErrorCount; }; int main() { try { // Lots of code here throw AnExceptionalClass(500); // Lots more code here } catch(AnExceptionalClass e) { cout << "Exceptional value is " << e.getErrorCount() << endl; } } AnExceptionalClass::AnExceptionalClass() {} AnExceptionalClass::AnExceptionalClass(int someErrorCount) : ErrorCount(someErrorCount) {} int AnExceptionalClass::getErrorCount() { return ErrorCount; }
In relation to the preceding code, you rarely catch exceptions by value. The recommended approach is to catch them by reference for the following good reasons:
- Polymorphism
- Efficiency
- Avoiding slicing
Allowing the handler to modify the exception object this might be necessary if the handler rethrows the exception.
The try block throws the exception in the form of an instance of the class AnExceptionalClass. Clearly, the exception thrown in Listing 11 is overkill! But it illustrates the idea of how an exception can occur in the middle of a block of code. The exception handler in the catch clause calls into a member function of the thrown class called getErrorCount().
Bear in mind that an uncaught exception will result in abnormal program termination. Let’s now take a look at a memory allocation exception handler in Listing 12.
Listing 12 C++ Memory Allocation Exception Handler
#define ARRAY_LIMIT 10000 struct aBigNode { int data[100]; aBigNode *link; }; typedef aBigNode* ABigNodePtr; aBigNode* aBigNodePtrArray[ARRAY_LIMIT]; try { for (int i = 0; i < ARRAY_LIMIT; i++) aBigNodePtrArray[i] = new aBigNode; cout << "No exceptions here\n"; } catch (std::bad_alloc &) { cout << "Ran out of memory\n"; } for (int i = 0; i < ARRAY_LIMIT; i++) delete aBigNodePtrArray[i];
Listing 12 shows a structure called aBigNode that we allocate dynamically into an array called aBigNodePtrArray. We do this until we run out of memory, at which point a bad_alloc exception is thrown. It’s that simple! Let’s now take a look at how you can write functions to throw exceptions.
Functions that Throw Exceptions and Callers that Catch Them
Listing 13 adds a function declaration just before main().
Listing 13 A Function that Throws an Exception
void aFunction(void) throw (AnExceptionalClass); void main()
Notice from Listing 13 that the declaration of aFunction() includes the exception that the routine throws – in this case (AnExceptionalClass). Listing 14 illustrates the definition of the function from Listing 13, which also lists the exception.
Listing 14 An Exception-Throwing Function Definition
void aFunction(void) throw (AnExceptionalClass) { //Code goes here //An exception condition occurs with error value CRITICAL_ERROR throw (AnExceptionalClass(CRITICAL_ERROR)); }
In Listing 14, the code encounters an error indicated by CRITICAL_ERROR. This error is pushed into the constructor of the exception class. Let’s put it all together in main() and call this new function in Listing 15 and see what happens.
Listing 15 Two Exception Handling Code Segments—One After the Other
void main() { try { aFunction(); } catch (AnExceptionalClass e) { cout << "Exceptional value is " << e.getErrorCount() << endl; } try { for (int i = 0; i < ARRAY_LIMIT; i++) aBigNodePtrArray[i] = new aBigNode; cout << "No exceptions here\n"; } catch (bad_alloc) { cout << "Ran out of memory\n"; }
The first thing that happens is a call to aFunction(), which results in an exception being thrown from within aFunction(). This exception is not caught by aFunction(). Instead, the exception is caught in the first catch block in main(). We caught the exception and avoided an abnormal termination. The code then executes the next try block. In other words, our code is weaving its way carefully through the choppy waters of exception handling and normal application flow. We’re taking account of code that can throw exceptions and we’re allowing for any such eventualities.
So, you can see that exception handling isn’t difficult and helps make code stronger by avoiding abnormal terminations. If an exceptional event occurs, such as running out of memory, you can at least detect the problem and exit gracefully. This is always preferable to an abnormal termination, which often leaves a poor impression on users and leaves programmers scratching their heads when attempting a fix! If you think you should use exception handlers in your code, you’re right!