DiscussionThe C++ Standard does not specify the way exception propagation has to be implemented, and there isn't even a de facto standard respected by most systems. The mechanics of exception propagation vary not only with the operating system and compiler used, but also with the compiler options used to build each module of your application with a given compiler on a given operating system. Therefore, an application must prevent exception handling incompatibilities by shielding the boundaries of each of its major modules, meaning each unit for which the developer can ensure that the same compiler and options are consistently used. At a minimum, your application must have catch-all catch(…) seals in the following places, most of which apply directly to modules:
Ensure that each module consistently uses a single error handling strategy internally (preferably C++ exceptions; see Item 72) and a single error handling strategy in its interface (e.g., error codes for a C API); the two might happen to be the same, but they usually aren't. Error handling strategies change only on module boundaries. Make clear how to interface the strategies between modules (e.g., how to interact with COM or CORBA, or to always catch exceptions at a C API boundary). A good solution is to define central functions that translate between exceptions and error codes returned by a subsystem. This way, you can easily translate incoming errors from peer modules into your internally used exceptions and ease integration. Using two strategies instead of one may sound like overkill and you might be tempted to forgo exceptions and only use the good old error codes throughout. But don't forget that exception handling has real ease-of-use and robustness advantages, is idiomatic C++, and can't be avoided in nontrivial C++ programs (because the standard language and library throw exceptions), so you should prefer to use exception handling when you can. For more details, see Item 72. A word of caution: Some operating systems use the C++ exception mechanism to piggyback low-level, system-specific errors, such as dereferencing a null pointer. Consequently, a catch(…) clause can catch more things than just straight C++ exceptions, and so your program might be in a twilight zone by the time the catch(…) is executing. Consult your system's documentation and either be prepared for handling such low-level exceptions in the smartest way you can devise, or use system-specific calls to disable such piggybacking at the start of your application. Replacing catch(…) with a series of catch(E1&) {/*…*/ }catch(E2&) {/*…*/ }… catch(En&) {/*…*/ } for each known base exception type Ei is not a scalable solution because you'd need to update the caught list whenever you add a new library (using its own exception hierarchy) to your application. Using catch(…) in places other than those listed in this Item is often a sign of bad design, because it means you are eager to catch absolutely all exceptions without necessarily having specific knowledge about how to handle them (see Item 74). A good program doesn't have many catch-alls and, indeed, not many try/catch statements at all; ideally, errors are propagated smoothly throughout, translated across module boundaries (a necessary evil), and handled at strategically placed boundaries. |