#include <FXException.h>
FXException is the base class for exception throws in FOX and is probably one of the most feature rich exception classes on any toolkit for C++. Features include:
FXERRH(5==a, "'a' is not five", MY_AISNOTFIVECODE, FXERRH_ISFATAL);
performs an assertion test FXERRG("File not found", FXEXCEPTION_NOTFOUND, 0);
generates an error and throws it FXException e=FXERRMAKE("Unknown exception", MY_UNKNOWNEXCEPTIONCODE, FXERRH_ISNORETRY);
generates an instance of FXExceptiontry
& catch
replaced with macros: FXERRH_TRY { // do something } FXERRH_CATCH(FXException &e) { FXERRH_REPORT(parentwnd, e) } FXERRH_ENDTRY
throw
with FXERRH_THROW() though this is usually done for you if you use any of the FXERRH or FXERRG macros. You can subclass FXException to create your own specialised exception types - indeed, Tn does this as do applications I have written which use TnFOX. As of v0.87, FXException is now a destructively copied class rather than the normal exception class it previously was. This was done for two reasons:
FXException has an accompanying source file munging script written in Python called CppMunge.py
. This useful tool can automatically extract error code macros as specified in your C++ sources and assign them unique constants into a header file (by default called ErrCodes.h) that are guaranteed to be unique to each class name. This greatly simplifies error handling across entire projects, irrespective of libraries used. You still need to include "ErrCodes.h"
in each of your source files using this feature. See this link for more details
The C++ standard states that if an exception is thrown during a stack unwind caused by the handling of an exception throw, the program is to exit immediately via std::terminate()
(ie; immediate exit without calling atexit()
registrants or anything else). The rationale behind this is that destructors permanently destroy state and so cannot be guaranteed restartable.
This writer personally finds this to be a silly limitation substantially decreasing the reliability of C++ programs using exceptions. While correct design has the programmer wrapping all destructors capable of throwing in a try...catch
section, a single missed wrapper causes immediate program exit - a situation which may arise once in a blue moon and thus be missed by testing. What is really needed is to change the C++ spec so that throwing any exception from any destructor is illegal - and then more importantly, have the compiler issue an error if code tries it. This would require modification of the linker symbol mangling system and thus break binary compatibility though.
Furthermore, this writer feels that as soon as you use ANY exceptions in C++, you must write all all destructors to be exception aware because as code grows in size, it becomes impossible to track whether code called by any non-trivial destructor throws or not. Best IMHO to assume that everything can throw, at all times. What annoys me most of all about the C++ standard action of immediately terminating the process is that there is no opportunity for saving out state or even providing debug information.
The C++ source code munger detailed above can optionally insert try...catch(FXException)
wraps around each and every destructor it processes (this can be prevented via placing FXERRH_NODESTRUCTORMOD
after the destructor definition). The wrap checks to see if an exception is currently being thrown for the current thread and if so, it appends the new exception and sets the FXERRH_HASNESTED and FXERRH_ISFATAL flags. Thus by default the program will report the error, clean up and exit.
It is possible to write restartable destructors in C++ - however, they won't always be of use to you eg; if the object is created on the stack. In that situation, an exception thrown implies unwinding the rest of the stack before it can be handled which clearly implies that that object can no longer exist in any useful sense.
An important point is that the next issue (time of writing: June 2003) of the C++ standard is likely to make all exception throwing from destructors illegal, so if you wish to future-proof your code you want to avoid it.
A major headache I had was what to do when an exception is thrown during static data initialisation and destruction. Firstly, I'll tell you the received wisdom - just don't do it! - your application is in an undefined state because static data is initialised and destroyed in a random order, so you can't guarantee anything at all! The facilities provided by FX::FXProcess_StaticInit are there precisely to permit non-trivial static data initialisation and destruction, so use them!
However, sometimes you can't escape running non-trivial code and it's better to reuse the library rather than duplicate the functionality in non-throwing code. For this situation I've had FXException print its report to stderr
on construction if static data is unavailable and it also disables its nested exception functionality (because it requires holding static state). This should cover informing the user of a problem rather than the generic and unhelpful message you usually get when terminate()
gets called.
First cardinal rule of all is to always assume every line of code can throw an exception. Not trying to be clever will prevent nasty bugs!
throw()
modifier - most modern compilers will avoid generating stack unwind code and thus efficiency is improved. I personally don't use any more complicated exception specifications because it's very hard to know what exceptions can be thrown in anything more than trivial code and besides, once one exception has to be handled the stack unwind code won't change much to handle all exceptions. new
-ed object. If an exception were thrown, the object would never be deleted. Use some sort of smart pointer which deletes its contents on destruct (eg; std::auto_ptr or FX::FXPtrHold) or a rollback action (FX::FBRBNew()). malloc()
, calloc()
etc. it throws a FX::FXMemoryException if a null pointer is returned. For new
it catches the std::bad_alloc
exception and converts it into a FX::FXMemoryException. The reason we can't use std::bad_alloc
natively is because the nested exception handling framework needs all exceptions to derive off FX::FXException. std::bad_alloc
might be thrown with FXEXCEPTION_STL1 and FXEXCEPTION_STL2. new
-ed only objects, write your destructors to be restartable. This implies code like the following: delete mymemberptr; // C++ spec guarantees delete 0; does nothing mymemberptr=0;
delete
on an object which previously threw an exception (obviously after fixing whatever caused the problem in the first place). Object::Object() : p1(0), p2(0) { FXRBOp unconstruct=FXRBConstruct(this); FXERRHM(p1=new ObjectPrivate1); FXERRHM(p2=new ObjectPrivate2); unconstruct.dismiss(); } Object::~Object() { FXDELETE(p2); // Really means "if(p2) { delete p2; p2=0; }" FXDELETE(p1); }
new
(ie; Object() : p(new ObjectPrivate)
as is so common in poorly written C++ - if an exception were to be thrown your destructor could not possibly know how to correctly cleanup the object. The other point is the rollback operation FXRBConstruct() which invokes your normal destructor if something goes wrong - however note that it only comes into effect after direct member initialisation which implies if that if non-trivial operations are done at that time, a thrown exception means no destruction. Watch out for this especially in copy constructors.
(auto_ptr) instead. The big problem with this is that smart pointers need to know the destructor of the type they point to which causes a major problem if your type is deliberately opaque. My solution removes that problem. An often suggested solution to this problem is to use function try blocks (if you can find a C++ compiler which supports them). They are of the form:
Object::Object() try : p1(new ObjectPrivate1), p2(new ObjectPrivate2) { ... } catch(exception e) { ... }