This guide focuses on the exception classes and macros made available by the ecl to the user.
Exceptions are usually avoided in control programs in favour of c style error handling (return values) because the stack winding/unwinding is too costly an overhead for control/embedded programs. However there are some instances where it is useful.
Constructors cannot usually provide a means to return with detailed information when something goes wrong - however they can with exceptions. This is especially useful for raii (resource acquisition is initialisation) classes where a class is completely initialised and enabled in the constructor. Since speed is not usually of the essence in program configuration and setup (particularly in control programs where only the control loop needs to be fast) many classes can take advantage of exception handling in their constructors in this phase of the program.
Another is to provide a path back up the program heirarchy to ascertain where bugs really occurred. Often a macro-function will only give you the point of failure, which is usually in a library object somewhere - it doesn't let you retrieve where your program caused the failure higher up. Throwing, rethrowing and catching exceptions, in debug mode, can do this.
However, again, be careful of using exceptions in areas of your code which need to be fast. The stack winding will slow your program down.
StandardException fulfills the general exception type used by the ecl. Anywhere you wish to throw this type of exception, you throw it by supplying the exception constructor some important information at the point of failure.
Typical output from this will be of the form:
Location : /mnt/froody/work/code/cpp/projects/ecl/modules/core/src/tests/exceptions.cpp:154 : /mnt/froody/work/code/cpp/projects/ecl/modules/core/src/tests/exceptions.cpp:151 Flag : There was a configuration error. Detail : Intentionally throwing in debug mode.
If you wish to manually catch and handle them, then you can implement a handler as follows.
Note the use of the flag handler within the if statement.
Another variant is the data exception which allows an additional data object to be bundled with the exception. This is a templatised class. Note, if you're going to output the error message, the data object needs to be streamable to the standard output (if not, you will get a compile time error).
Usually its tedious tracking down the error number and matching it with a particular function call's errors. There is the generic c strerror() function which can match a generic posix errno value with a string, but this is still generic. To make this more convenient, the PosixErrorHandler class provides a primary template for posix error number conversions to custom StandardException types that can be thrown. As a fallback this will use by default the strerror() function to generate a posix styled StandardException.
The conversion is done via a static function within the specialisation, GenerateStandardException().
Default case example:
This automatically collects the current error value stored in the global errno variable and uses strerror() to generate a message for the StandardException.
An example of a specialised instance is that used for TimeStamp where the clock_gettime function can generate posix errors:
Use as close as possible to the posix function call:
You can also rethrow a StandardException, useful if you want to track the source of the exception as it goes back up the program heirarchy. This is done by passing the old exception to the constructor call of a new throw.
Exceptions in any of the ecl libraries can be compiled out by simply passing the -DECL_DISABLE_EXCEPTIONS to cmake when compiling the ecl libraries (not your program!). If building in ros you can set this variable in $ROS_ROOT/rosconfig.cmake. Note that this variable is picked up by ecl_config where it sets a macro that is used throughout by the ecl libraries.
When you disable ecl exceptions, all ecl classes are designed in such a way so that they can gracefully fall back to a lower form of error handling.
Exceptions can also be enabled exclusively for debug mode (so long as NDEBUG is not defined) with the debug_throw and debug_throw_decl macros. When NDEBUG is defined, the throw will automatically disappear from the program code.
Catching can be done with the usual try-catch blocks, but if you want to pre-process this code away as well, you can use the debug_try and debug_catch macros.
In addition, you may use the assert_throw and assert_throw_decl macros (a mechanism similar to the run_time_assert function in ecl_errors) as follows:
The use of exceptions here doesn't exactly conform to the c++ 'ideal' for exception handling in a library. For that you should derive from the stdexcept classes, particularly runtime_error and logic_error and have a single class for each type of error (we've bundled all the ecl standard errors into two classes here - Standard and Data. However I can't seem to find a really good reason at this point in time to circumvent the practicality of ecl_exceptions - it's just effectively simple to use for the ecl. I think this is because the purpose of this library is to present a standard set of error types across platforms, no need to keep making new exception classes for every new class. package or platform implementation in the ecl. The only reason that might motivate a change in policy is if the ecl is used in very heirarchical programs which require floating different exception types to different levels depending on their type (e.g. you might want to catch an _is_busy_ error early and recover, whilst letting a _configuration_error_ float to the top).