Consideration When Throwing an Exception

A common approach to handling errors is to return some kind of error code from the function in which the problem is first detected. However, in the worst-case scenario, a function that is 40 calls deep in the call stack might detect a problem that can only be handled by the top-level loop, or by main(). In this scenario, every one of the 40 functions on the call stack would need to be written so that it can pass an appropriate error code all the way back up to the top-level error-handling function.

principle

One solution is to throw an exeption, which is a very powerful feature of C++. When an exception is thrown, relevant information about the error is placed into a data object of the programmer’s choice known as an exception object. The call stack is then automatically unwound, in search of a calling function that has wrapped its call in a try-catch block. If a try-catch block is found, the exception object is matched against all possible catch clauses, and if a match is found, the corresponding catch’s code block is executed. The destructors of any automatic variables are called as needed during the stack unwinding process.

overhead

But it is important to know that exception handling does add some overhead to the program. The stack frame of any function that contains a try-catch block must be augmented to contain additional information required by the stack unwinding process. Also, if even one function in the program or the library that the program links with uses exception handling, the entire program must use exception handling. This is because the compiler cannot know which functions might be above the call stack when throwing an exception.

workaround

To avoid the entire program having to be written with exceptions enabled, it is possible to wrap all these API calls into the libraries in the new functions, and enable exception handling in a translation unit where these functions are implemented. Each of these new wrapping functions would catch all possible exceptions in a try-catch block and convert them into error return codes. Any code that links with this wrapper library can therefore safely disable exception handling.

pitfalls

Arguably more important than the overhead issue is the fact that exceptions are in some ways no better than goto statements. A function that neither throws nor catches exceptions may nevertheless become involved in the stack unwinding process if it finds itself sandwiched between such functions in the call stack. And the unwinding process is itself imperfect. The software can easily be left in an invalid state unless the programmer considers every possible way that an exception can be thrown, and handles it appropriately.

Another issue is its cost. Although in theory modern exception handling frameworks do not introduce additional runtime overhead in the error-free case, this is not necessarily true in practice. For example, the code that the compiler adds to functions for unwinding the call stack when an exception occurs tends to produce an overall increase in code size. This might degrade I-cache performance, or cause the compiler to decide not to inline a function that it otherwise would have.

References

[1] J. Gregory, Game Engine Architecture, Third Edition, CRC Press

[2] Exceptions vs. status returns

[3] http://www.joelonsoftware.com/items/2003/10/13.html

[4] https://www.joelonsoftware.com/2003/10/15/15-2/


© 2024. All rights reserved.