Usage
The body of each function that needs to be monitored must be enclosed by the FUNCTION_START and FUNCTION_END macros. The FUNCTION_START macro also requires a parameter that represents the function's name; it will be used later by FUNCTION_END if a catch and throw happens.
The catch block that is able to process the exception must have a call to exceptionsManager::getExceptionInfo()
or exceptionsManager::getMessage()
. Both are static methods.
The first method returns a list of exceptionInfo
objects that store the information of the catch and throw points traversed by the exception, and the second method returns a string with a text representation of the objects returned by getExceptionInfo()
. Both the methods clear the information list, so subsequent calls to getExceptionInfo()
or getMessage()
will return an empty list or an empty string.
When throwing an exception, the application's code should use the macro FUNCTION_THROW (although is not necessary), which also logs some information related to the place where the original throw happened.
The library can be compiled on Windows (you have to define the preprocessor symbol WIN32) and on Posix systems.
Example
The complete source code includes the exceptionsManager
class, the related macros and a simple console application that executes an integer division between two parameters.
When an exception is thrown, then all the points traversed by the exception are logged and finally displayed on the screen by the outer catch statement. You can try the application and make it fail by specifying less or more than two parameters, or trying to divide by zero.
Listing One shows two functions that include the exceptionsManager macros, and Listing Two shows how the exception can be managed and how to retrieve the stack dump of the caught exceptions.
Listing One
#include "exception.h" //... int function1(int left, int right) { FUNCTION_START("function1"); if(right==0) { throw std::runtime_error("Ops, dividing by 0!"); } return left/right; FUNCTION_END(); } int function0(int left, int right) { FUNCTION_START("function0"); return function1(left, right); FUNCTION_END(); }
Listing Two
try { return function0(param0, param1); } catch(std::runtime_error&) { std::cout << "An error occurred. Stack dump:\n"; std::cout << exceptionsManager::getMessage(); }
Example 1 shows a typical output of the stack dump.
Example 1.
c:\>cuj.exe 10 0 An error occurred. Stack dump: [function1] file: c:\sourcecode\cuj\cuj.cpp line: 25 exception type: class std::runtime_error exception message: Ops, dividing by 0! [function1] file: c:\sourcecode\cuj\cuj.cpp line: 28 exception type: class std::runtime_error exception message: Ops, dividing by 0! [function0] file: c:\sourcecode\cuj\cuj.cpp line: 37 exception type: class std::runtime_error exception message: Ops, dividing by 0!
Conclusion
The exceptionsManager
class and the related macros achieve the same results as chained exceptions, but without the problems introduced by chained exceptions.
You don't have to change your code; your catch statements can continue to look for the same kind of exceptions. There are no exceptions wrapped inside other exceptions, and everything works as it did before.
While it is true that you have to insert the two macros in the functions that have to be monitored, this is also true with chained exceptions, so the overall amount of effort is comparable in the two approaches.
Paolo is a C++ software developer specializing in image analysis and medical image formats. He distributes his products through his web site www.puntoexe.com, and can be contacted at [email protected]