This module contains classes that are used in the exception mechanism of deal.II.
Exceptions are used in two different ways:
Static assertions: These are checks that are only enabled in debug mode, not in optimized (or production) mode. They are meant to check that parameters to functions satisfy certain properties and similar assertions. For example, static assertions are used to make sure that two vectors that are added together have the same number of components -- everything else would not make any sense anyway.
Such checks are performed by the Assert macro in several thousand places within the library. Also, several tutorial programs starting with step-5 show how to do this.
If a static assertion is violated, the exception mechanism generates an exception of a type that indicates what exactly goes wrong, displays appropriate information, and then aborts the program -- if you try to add two vectors of different length, there is nothing that can be done within the program to cope with the situation, you have to go fix the program code instead. The exceptions of this module are used to indicate the reason for the failure.
Dynamic assertions: These are used to check dynamic features, such as whether an output file can be written to. These are things that can't be checked statically, i.e. they may change from program run to program run. It is therefore insufficient to only check these situations in debug mode.
Rather, one has to check them every time during execution of a program. Within deal.II, this is done using the AssertThrow macro introduced in step-9, step-13, and following tutorial programs. The macro checks a condition, and if violated throws an exception of one of the types declared in this module, using the C++ throw
mechanism. Since these are run-time exceptions, this gives the program the chance to catch the exception and, for example, write the output to a writable file instead.
The error handling mechanism in deal.II
is generally used in two ways. The first uses error checking in debug mode only and is useful for programs which are not fully tested. When the program shows no errors anymore, one may switch off error handling and get better performance by this, since checks for errors are done quite frequently in the library (a typical speed up is a factor of four!). This mode of exception generation is most useful for internal consistency checks such as range checking or checking of the validity of function arguments. Errors of this kind usually are programming errors and the program should abort with as detailed a message as possible, including location and reason for the generation of the exception.
The second mode is for error checks which should always be on, such as for I/O errors, failing memory requests and the like. It does not make much sense to turn this mode off, since this kind of errors may happen in tested and untested programs likewise. Exceptions of this kind do not terminate the program, rather they throw exceptions in the C++
manner, allowing the program to catch them and eventually do something about it. As it may be useful to have some information printed out if an exception could not be handled properly, additional information is passed along as for the first mode. The latter makes it necessary to provide a family of macros which enter this additional information into the exception class; this could in principle be done by the programmer himself each time by hand, but since the information can be obtained automatically, a macro is provided for this.
Both modes use exception classes, which need to have special features in additional to the C++
standard's std::exception
class. Such a class is declared by the following lines of code:
DeclException2 (ExcDomain, int, int, << "Index= " << arg1 << "Upper Bound= " << arg2);
This declares an exception class named ExcDomain
, which has two variables as additional information (named arg1
and arg2
by default) and which outputs the given sequence (which is appended to an std::ostream
variable's name, thus the weird syntax). There are other DeclExceptionN
macros for exception classes with more or no parameters. By convention, the name of all exception classes starts with Exc...
and most of them are declared locally to the class it is to be used in (a few very frequently found ones are also declared in the StandardExceptions namespace and are available everywhere). Declaring exceptions globally is possible but pollutes the global namespace, is less readable and most of the time unnecessary.
Since exception classes are declared the same way for both modes of error checking, it is possible to use an exception declared through the DeclExceptionN(...)
macro family for both static as well as dynamic checks.
To use the exception mechanism for debug mode error checking, write lines like the following in your source code:
Assert (n<dim, ExcDomain(n,dim));
which by macro expansion does essentially the following:
#ifdef DEBUG if (!(cond)) issue error of class ExcDomain(n,dim) #else do nothing #endif
i.e. it issues an error only if the preprocessor variable DEBUG
is set and if the given condition (in this case n < dim
is violated).
If the exception was declared using the DeclException0 (...)
macro, i.e. without any additional parameters, its name has nonetheless to be given with parentheses: Assert (i>m, ExcSomewhat());
If the DEBUG
preprocessor directive is set, the call Assert (cond, exc);
is basically converted by the preprocessor into the following sequence:
if (!(cond))
deal_II_exceptions::internals::issue_error_assert_1
(__FILE__,
__LINE__,
__PRETTY_FUNCTION__,
#cond,
#exc,
&exc);
(Note that function names and exact calling sequences may change over time, but the general principle remains the same.) I.e., if the given condition is violated, then the file and line in which the exception occured as well as the condition itself and the call sequence of the exception object is passed to the deal_II_exceptions::internals::issue_error_assert_1() function. Additionally an object of the form given by exc
is created (this is normally an unnamed object like in ExcDomain (n, dim)
of class ExcDomain
) and transferred to this function.
__PRETTY__FUNCTION__
is a macro defined by some compilers and gives the name of the function. If another compiler is used, we try to set this function to something reasonable, if the compiler provides us with that, and "(not available)"
otherwise.
In issue_error_assert
, the given data is transferred into the exc
object by calling the set_fields() function; after that, the general error info is printed onto std::cerr
using the PrintError() function of exc
and finally the exception specific data is printed using the user defined function PrintError() (which is normally created using the DeclException (...)
macro family. If it can be obtained from the operating system, the output may also contain a stacktrace to show where the error happened. Several of the Tutorial programs programs show a typical output.
After printing all this information, deal_II_exceptions::internals::abort() is called (with one exception, see the end of this section). This terminates the program, which is the right thing to do for this kind of error checking since it is used to detect programming errors rather than run-time errors; a program can, by definition, not recover from programming errors.
If the preprocessor variable DEBUG
is not set, then nothing happens, i.e. the Assert
macro is expanded to {}
.
Sometimes, there is no useful condition for an exception other than that the program flow should not have reached a certain point, e.g. a default
section of a switch
statement. In this case, raise the exception by the following construct:
Assert (false, ExcInternalError());
See the step-7 and several other of the tutorial programs for a use of this construct.
As mentioned above, the program is terminated once a call to Assert
fails. However, there is one case where we do not want to do this, namely when a C++ exception is active. The usual case where this happens is that someone throws an exception through the AssertThrow
mechanism (see below) which, while the stack is unwound, leads to the destruction of other objects in stack frames above. If other objects refer to the objects being thus destroyed, some destructors raise an exception through Assert
. If we would abort the program then, we would only ever see the message that an object is being destroyed which is still referenced from somewhere, but we would never see the original exception that triggered this. (You can see it in the debugger by putting a break point on the function __throw
, but you cannot see it from the program itself.) In that case, we use a C++ standard library function to detect the presence of another active exception and do not terminate the program to allow that the thrown exception propagates to some place where its message can be displayed.
Since it is common that one failed assertion leads to a whole chain of others, we only ever print the very first message. If the program is then aborted, that is no problem. If it is not (since a C++ exception is active), only the first is displayed and a message about suppressed follow-up messages is shown.
For this mode, the standard C++
throw
and catch
concept exists. We want to keep to this, but want to extend it a bit. In general, the structure is the same, i.e. you normally raise and exception by
if (!(cond)) throw ExcSomething();
and catch it using the statement
try { do_something (); } catch (exception &e) { std::cerr << "Exception occured:" << std::endl << e.what () << std::endl; do_something_to_reciver (); };
exception
is a standard C++
class providing basic functionality for exceptions, such as the virtual function what()
which returns some information on the exception itself. This information is useful if an exception can't be handled properly, in which case as precise a description as possible should be printed.
The problem here is that to get significant and useful information out of what()
, it is necessary to overload this function in out exception class and call the throw
operator with additional arguments to the exception class. The first thing, overloading the what
function is done using the DeclExceptionN
macros, but putting the right information, which is the same as explained above for the Assert
expansion, requires some work if one would want to write it down each time:
if (!(cond)) { ExcSomething e(additional information); e.set_fields (__FILE__, __LINE__, __PRETTY_FUNCTION__, "condition as a string", "name of condition as a string"); throw e; };
For this purpose, the macro AssertThrow
was invented. It does mainly the same job as does the Assert
macro, but it does not kill the program, it rather throws an exception as shown above. The mode of usage is
AssertThrow (cond, ExcSomething(additional information));
The condition to be checked is incorporated into the macro in order to allow passing the violated condition as a string. The expansion of the AssertThrow
macro is not affected by the DEBUG
preprocessor variable.
There is a whole family of DeclExceptionX
macros where X
is to be replaced by the number of additional parameters (0 to 5 presently). These macros are used to declare exception classes in the following way:
DeclException2 (ExcDomain, int, int, << " i=" << arg1 << ", m=" << arg2);
The first argument denotes the name of the exception class to be created. The next arguments are the types of the parameters (in this case there two types, corresponding to the X
in DeclExceptionX
) and finally the output sequence with which you can print additional information.
The syntax of the output sequence is a bit weird but gets clearer once you see how this macro is defined (again schematically, actual function names and definitions may change over time and be different):
class name : public ExceptionBase { public: name (const type1 a1, const type2 a2) : arg1 (a1), arg2(a2) {}; virtual void print_info (std::ostream &out) const { out outsequence << std::endl; }; private: type1 arg1; type2 arg2; };
If declared as specified, you can later use this exception class in the following manner:
int i=5; int m=3; Assert (i<m, MyExc2(i,m));
and the output if the condition fails will be
-------------------------------------------------------- An error occurred in line <301> of file <exc-test.cc>. The violated condition was: i<m The name and call sequence of the exception was: MyExc2(i,m) Additional Information: i=5, m=3 --------------------------------------------------------
Obviously for the DeclException0(name)
macro, no types and also no output sequence is allowed.