At the time of writing (June 2003), a majority of programmers just prefer to label fully exception-aware code as a mixture of being impossible or too much work/overhead to implement. However, it is my belief that like pervasive multithreading and multiprocessing, fully exception-aware code will become the defacto standard in the future. The parts of TnFOX written by me are all fully exception-aware.
Before I began TnFOX, I wanted to implement transaction support. Basically that means replacing those custom-written little classes with something more convenient. By sheer coincidence, I happened to fail a job interview with Trolltech because I didn't know anything about compile time metaprogramming, so this led me to buying Andrei Alexandrescu's Modern C++ Design which then led me to think I could solve this by getting the compiler to do it for me.
However, searching on google showed that Andrei Alexandrescu had had exactly the same problem and in his December 2000 CUJ article he gave an almost entirely compile-time solution (as much as it could be). From some ideas in that, I have come up with the functions listed below which can be almost as efficient as Andrei's when no rollbacks occur (around 15-20 instructions in x86). Furthermore, it offers a considerable superset of functionality including you not needing to FXRESTRICT your rollbacks to the local scope.
Firstly, if you merely want a pointer deleted on scope exit, please see FX::FXPtrHold which is much more useful than at first sight (it can used statically to clean up fire-and-forget static allocations). For more complex operations such as the calling of a specific function or method with certain parameters, then FXRollback is the right choice.
Usage is ridiculously easy. For example:
{ QPtrList<Item> *mylist; // I need to atomically append "item" to three lists QMtxHold h(mylist); mylist->append(item); FXRBOp undomylist=FXRBObj(*mylist, &QPtrList<Item>::removeRef, item); myotherlist->append(item); FXRBOp undomyotherlist=FXRBObj(*myotherlist, &QPtrList<Item>::removeRef, item); yetotherlist->append(item); undomyotherlist.dismiss(); undomylist.dismiss(); }
myotherlist
or yetotherlist
throws an exception, the rollback operations are destructed and thus the operation is undone - thus data integrity is maintained. For functions: FXRollbackOp appfatalbit=FXRBFunc(::exit, 1); ... bits which could throw an exception and the app would have to immediately exit appfatalbit.dismiss(); // It's over
You should try to use the above wherever possible as they are written to be extremely efficient in both code size and run-time overhead (in the normal case, all code executed is inlined). However there are times when you have a complex set of variable operations to be undertaken, all of which need to be rolled back on exception. The efficient use above cannot be used because FXRollbackOp must be created at the point of declaration ie; you can't declare an empty FXRollbackOp in a higher scope and assign to it during an if() statement say (this is because of the way FXRollbackOp is implemented to avoid a virtual destructor call). In this situation, you should declare a FXRollbackGroup and add to it:
void class::method(Item *i) { FXRollbackGroup rbg; if(shouldAlterA) { alist1.append(i); rbg.add(FXRBObj(alist1, &QPtrList<Item>::removeRef, i)); } if(shouldAlterB) { blist1.append(i); rbg.add(FXRBObj(blist1, &QPtrList<Item>::removeRef, i)); } ... rbg.dismiss(); }
try
...finally operation - simply don't dismiss().As of v0.80, you can pass a null code pointer to disable the operation eg;
int refCount=insertObject(obj); FXRBOp uninsert=FXRBObj(*this, refCount>1 ? 0 : &Me::killObject, obj);
Of course the big danger when using destructors to roll back operations interrupted by an exception being thrown is the dreaded ISO C++ guaranteed std::terminate()
if another exception happens. However, TnFOX's superior nested exception support means that that is guaranteed not to happen with proper use of CppMunge.py <yay!>.
To guard against not cleaning up partially constructed objects, you should place a FXRBConstruct(this)
first thing in any constructor which might throw an exception and use my two-stage object construction idiom. Further details are given in the doc page for FX::FXException.
Classes | |
class | FX::FXRollbackGroup |
Lets you place a rollback in a group and/or a non-local scope. More... | |
Functions | |
FXRollbackOp | FX::FXRBFunc (function,[, par1 &[, par2 &[, par3 &[, par4 &]]]]) |
FXRollbackOp | FX::FXRBObj (object, ptrtomethod,[, par1 &[, par2 &[, par3 &[, par4 &]]]]) |
FXRollbackOp | FX::FXRBNew (ptrtonewedalloc &) |
FXRollbackOp | FX::FXRBNewA (ptrtonewedalloc &) |
FXRollbackOp | FX::FXRBAlloc (ptrtomem) |
FXRollbackOp | FX::FXRBConstruct (thisptr) |