Introduction to the Use of Zoom Exceptions W E Brown, 30-Oct-1997, last revised 5-Jan-1998 1. Introduction --------------- This summary describes the mechanics decided on for creating and throwing a ZMexception as implemented by the zoom Exceptions package. Note that all public C++ symbols used within this Exceptions class begin with the unlikely prefix "ZMex" (or, in the case of the preprocessor, "ZMEX") in order to help avoid namespace pollution. For example, we use "ZMexception" as the name of the class from which all other exception classes are (directly or indirectly) derived. Additionally, all ZOOM-generated ZMexception classes will use at least "ZMx" as their name prefix. More typically, to avoid internal name clashes, the names start with a short string identifying the package, e.g. "ZMxHep" for HepTuple, or "ZMxpv" for the PhysicsVectors package. It is recommended that users defining their own ZMexceptions establish and employ some similar convention. 2. How to declare and define a new exception class -------------------------------------------------- Declaring/defining a new exception class is done as follows (code adapted from Exceptions/test/exctest1.cc): // Required header, with recommended code guard: #ifndef ZMEXCEPTION_H #include "Exceptions/ZMexception.h" #endif // ZMEXCEPTION_H // Required (sample) declaration (for use in a .h file): ZMexStandardDefinition( ZMexception, ZMxOops ); // Defines class ZMxOops : public ZMexception { ... }; // Required (sample) definition (for use in a .cc file): ZMexClassInfo ZMxOops::_classInfo( "Oops", "ExcTest", ZMexWARNING ); // Provides certain details specific to the new class In the example above: 1) ZMxOops is the name of the newly-defined exception class. Note that this name appears in three places; it is important to be consistent. 2) ZMexception is the name of the parent exception class of ZMxOops; any previously defined ZMexception may be used as the parent. 3) "Oops" is the exception's name as it is to appear in the log. Such a quoted exception name string should for clarity be closely related to the actual name but, as shown here, might omit some package- identifying baggage. 4) "ExcTest" is the logged message prefix (normally indicating the package, facility, or program giving rise to the message). 5) ZMexWARNING is the default severity level of ZMxOops. (See below for a complete list of possible severity levels and the intended significance of each.) 3. Constructing/throwing an instance of the new exception class --------------------------------------------------------------- This Exceptions package provides a facility, ZMthrow(), to make use of ZMexception and its descendent classes. Before using this capability, insert the following line to provide the necessary declaration: #include "Exceptions/ZMthrow.h" Thereafter, an exception of a class defined as shown above is typically constructed and thrown within a single statement, such as: ZMthrow( ZMxOops("Ouch!") ); Here, "Ouch!" may be arbitrary text to be associated with this particular occurrence (exception instance). The text will be logged, as described below. 4. Resulting log message ------------------------ Assuming that the ExcTest program has been compiled with appropriate compiler switches that enable use of exceptions, the logged message resulting from the above ZMthrow(...) example will be: ExcTest-W-Oops [#1] Ouch! Thu Oct 30 16:17:07 1997 -- ZMthrow was issued from exctest1.cc line 21 ... Exception thrown! The parts of this message are interpreted as follows: 1) ExcTest the message came from the ExcTest facilty; 2) -W- it is considered a Warning; 3) Oops it is classified as an Oops occurrence; 4) [#1] this is the first instance of such an occurrence; 5) Ouch! this text is associated with this specific occurrence; 6) Thu ... 1997 timestamp; 7) ...exctest1.cc this occurrence arose from this associated compilation unit; 8) line 21 identifies the specific line of source text giving rise to this occurrence; 9) ... thrown gives the run-time disposition of the exception. 5. Available severity levels ---------------------------- Here is a list of the seven severity levels that are available for use in defining a new exception class. Each severity is accompanied by a description of its intended interpretation, as documented in the Exceptions/ZMexSeverity.h header file: ZMexNORMAL All is well; always safe to ignore; typically not worth logging since it's probably just a temporary placeholder. ZMexINFO In the normal course of events, here is news worth logging; always safe to ignore; often useful for progress reporting and for debugging purposes. ZMexWARNING Something unusual has happened, but we have a quite reasonable action to take; it's generally safe to ignore the warning because you'll probably get just about the result you intended; logging can probably cease after (say) 50 of the same warnings. ZMexERROR We encountered something such that, although we can make it safe to continue running (e.g., by supplying a default value instead of a value we can't for some reason calculate), you probably won't get the result you expected unless you handle this yourself; ought always be logged (but may be sensible, if hundreds of the same error are intentionally ignored, to stop logging each one). ZMexSEVERE The action you intended will almost certainly have a seriously flawed outcome and we doubt that either we or you can make it safe to continue if you ignore this; ought always be logged. ZMexFATAL We can make no representations as to the state of any part of the software, even of software parts not obviously associated with the failed intended action and even if you try to handle the problem; ought always be logged and essentially never be ignored. ZMexPROBLEM The software has reached a logically impossible internal state; must always be logged and never be ignored; if encountered, should always be reported to the software's developers and/or maintainers. 6. Using handlers In the Exceptions package, a handler is the term for an instance of a class that processes a ZMthrow'n exception. A handler is responsible for having the exception instance logged; for taking any remedial action appropriate to the exception instance; and for determining whether the exception instance can safely be ignored by the user code. The Exceptions package includes a number of pre-defined handlers (listed below), implementing several commonly-wanted behaviors. Each exception class is associated with a handler to be applied to all ZMthrow'n instances of that class. By default, this handler implements the behavior known as ZMexHandleViaParent(); this applies the behavior -- whatever it may be -- of the parent exception class' handler to the current exception class. A user may change this behavior in two ways. A different handler may be associated with an exception class when the class is defined: ZMexClassInfo ZMxOops::_classInfo( "Oops", "ExcTest", ZMexWARNING, ZMexIgnoreAlways() ); Alternatively, the handler associated with an exception class may be changed dynamically: #include "Exceptions/ZMexHandler.h" ZMexOops::setHandler( ZMexIgnoreAlways() ); The given behavior will apply to any exceptions ZMthrow'n after the handler has been established. 7. Available handlers Here is a list of the five standard handlers that are defined via the Exceptions package. Each is accompanied by a brief description of its behavior: ZMexThrowAlways() the ZMthrow'n exception instance will, after handling, become the object of a C++ throw. ZMexIgnoreAlways() the ZMthrow'n exception instance will be handled, but will have no further affect on subsequent control flow. ZMexThrowErrors() the ZMthrow'n exception instance will, after handling, be thrown if its severity is ZMexERROR or higher, but be ignored if of a lesser severity. Note: this is the default handling behavior of the package's ZMexception class, the intended (direct or indirect) ancestor class of all other exception classes. ZMexIgnoreNextN( n ) the next n occurrences of a ZMthrow'n instance of this class will be ignored after handling; subsequent instances will be thrown after handling. ZMexHandleViaParent() the ZMthrow'n exception instance will be handled by the handler currently associated with the parent exception class. 8. Using loggers In the Exceptions package, a logger is the term for an instance of a class that records, to a designated destination, a ZMthrow'n exception. A logger is responsible only for routing the message associated with an exception instance; it is not responsible for determining or formatting any message. The Exceptions package includes a few pre-defined loggers (listed below), implementing several commonly-wanted logging behaviors. Each exception class is associated with a logger to be applied to all ZMthrow'n instances of that class. By default, this logger implements the behavior known as ZMexLogViaParent(); this applies the behavior -- whatever it may be -- of the parent exception class' logger to the current exception class. A user may change this behavior in two ways. A different logger may be associated with an exception class when the class is defined: ZMexClassInfo ZMxOops::_classInfo( "Oops", "ExcTest", ZMexWARNING, ZMexIgnoreAlways(), ZMexLogAlways() ); Alternatively, the logger associated with an exception class may be changed dynamically: #include "Exceptions/ZMexLogger.h" ZMexOops::setLogger( ZMexLogAlways() ); The given behavior will apply to any exceptions ZMthrow'n after the logger has been established, provided the handler invokes the logger. 9. Available loggers Here is a list of the standard loggers that are defined via the Exceptions package. Each is accompanied by a brief description of its behavior: ZMexLogAlways( ostream & Dest = cerr ) All ZMthrow'n exception instances processed by this logger will appear in the designated destination. Note: this is the default logging behavior of the package's ZMexception class, the intended (direct or indirect) ancestor class of all other exception classes. ZMexLogTwice( ostream & Dest1, ostream & Dest2 = cerr ) All ZMthrow'n exception instances processed by this logger will appear in both designated destinations. ZMexLogNever() No ZMthrow'n exception instances processed by this logger will appear in any designated destination. ZMexLogViaParent() All ZMthrow'n exception instances processed by this logger will be logged by the logger currently associated with the parent exception class. 10. Exception history This Exceptions package records, by default, all exceptions that have been ZMthrow'n. Known as ZMerrno, this capability is in addition to the logging described earlier, and allows user code to interrogate and make decisions based on such exceptions. To use this ZMerrno facility, insert the following line to provide the necessary declaration: #include "Exceptions/ZMerrno.h" This declaration makes the following operations (functions) available: 1) ZMerrno.setMax( unsigned int limit = 100 ) Sets the maximum number of ZMthrow'n exceptions to be recorded. Once this limit has been reached, each subsequently ZMthrow'n exception will cause the oldest (least recent) recorded exception to be forgotten (i.e., discarded from ZMerrno's list). The default limit is 100. If this limit is acceptable, user code need not call this function at all. If the limit is set to 0, this ZMerrno facility is effectively disabled, and any recorded exceptions are immediately permanently discarded. 2) ZMerrno.write( const ZMexception & exc ) Records the given exception. This is typically taken care of by the exception handler; thus, user code seldom needs to call this function directly. 3) ZMerrno.count() Return the (integer) number of ZMthrow'n exceptions ever recorded via ZMerrno.write(), whether or not they are still recorded. 4) ZMerrno.size() Return the (integer) number of ZMthrow'n exceptions currently recorded. 5) ZMerrno.clear() Set an internal counter to zero. This counter is available (see next function) to user code to track ZMthrow'n exceptions that have occurred during any arbitrary time interval. 6) ZMerrno.countSinceCleared() Return the (integer) number of ZMthrow'n exceptions that have been recorded via ZMerrno.write(), whether or not they are still recorded, since the user counter was last cleared (see previous function). 7) ZMerrno.name( unsigned int k = 0 ) Return the name (as logged) of the latest-but-k exception currently recorded via ZMerrno. Thus, ZMerrno.name() gives the (string) name of the latest recorded exception, while ZMerrno.name(1) gives the name of the exception recorded immediately before the last one. 8) ZMerrno.get( unsigned int k = 0 ) Return a (const pointer to) the latest-but-k exception currently recorded via ZMerrno. Thus, ZMerrno.get() gives a (const pointer to) the latest recorded exception, while ZMerrno.get(1) gives the corresponding pointer to the exception recorded immediately before the last one. This may be useful to peruse the exception's message text, to note the handler and logger used when the exception was ZMthrow'n, etc. The resulting pointer should generally be checked against 0, in case ZMerrno does not go back as far as requested. 9) ZMerrno.erase() Remove the most recently-recorded exception. This can be useful if, for example, there is a loop of known ignorable exceptions, all nonetheless duly recorded by the handler. These exceptions can be erase()'d so as not to wipe out history for other, more interesting, exceptions.