My thought is this as follows. Please keep in mind I am writing all code for this on the fly so it may not be perfect. :)
It's actually probably easier to explain in code, but I'll try to give the gist first. Since you are not interested in catch (...)
I have not focused on detecting that, however, I think it would be relatively easy to modify the idea to handle that as well. (Note: originally I was going to use a pointer to the function as the way to tell which function you are in, but I have ended up going with the name because I wasn't thinking about virtual functions. I'm sure this can all be optimized if necessary though.)
Create the following:
- a class with static versions of your desired functions
- a special custom "stack" type to hold your exception information with operations that can tear the stack down based on that info
- a struct containing a type strings and a
void pointer string to hold the name of the function it was created in
Setup:
- prior to your
try
, place a catch type struct with the type name being caught as well as with all exceptions caught in this function on the stack, along with a pointer to the name of the function.
- the string to set for type is determined by quoting the type (so
"..."
is fine for default catches).
- initially I was playing around with the idea of using
typeid
to get the undecorated type names, then using .raw_name()
to get the mangled name
- but it won't work for native types, or for non-virtual types, and there's really no need for the mangled names, so it's kind of pointless for this implementation
Teardown:
- in every
catch
block, at the top, tear the stack down one beyond the type you are catching for the function you are in
- following the last
catch
blocks in a function, tear the stack down one beyond the first instance of the teardown in the last catch
The main issue with this solution is that it is clearly very cumbersome.
One solution is [bet you saw this coming] macros.
ExceptionStackHandler.h
// ...
// declaration of the class with the needed functions, perhaps
// inline definitions. the declaration of the stack. etc.
// ...
#if __STDC__ && __STDC_VERSION__ >= 199901L
#define FN_NAME __func__
#else
#define FN_NAME __FUNCTION__
#endif
// was thinking would be more to this; don't think we need it
//#define try_code(code) try { code }
// this macro wraps the code such that expansion is not aborted
// if there happen to be commas in the code.
#define protect(code) if (true) { code }
// normal catch and processing
#define catch_code(seed_code, catch_type, catch_code) \
ExceptionStackHandler.Stack.Push(exceptionItem(#catch_type, FN_NAME)); \
seed_code \
catch (catch_type Ex) \
{ \
ExceptionStackHandler.Stack.PopThrough(#catch_type, FN_NAME); \
catch_code \
}
// you *must* close a try with one of the following two calls, otherwise
// some items may be missed when clearing out the stack
// catch of all remaining types
#define close_catchall(seed_code, last_catch_type, catch_code) \
seed_code \
catch (...) \
{ \
ExceptionStackHandler.Stack.PopThrough(#last_catch_type, FN_NAME); \
catch_code \
} \
ExceptionStackHandler.Stack.PopThrough(#last_catch_type, FN_NAME); \
// cleanup of code without catching remaining types
#define close_nocatch(last_catch_type, catch_code) \
seed_code \
ExceptionStackHandler.Stack.PopThrough(#last_catch_type, FN_NAME)
Then in your code it would look like
bool isTheRoofOnFire(bool& isWaterNeeded)
{
// light some matches, drip some kerosene, drop a zippo
close_nocatch
(
catch_code
(
catch_code
(
//--------------------------------------------------------
// try block for the roof
try
{
protect (
// we don't need no water
if (isWaterNeeded)
isWaterNeeded = false;
)
}
// End Try Block
//--------------------------------------------------------
,
//--------------------------------------------------------
// catch(string Ex)
string,
protect (
if (Ex == "Don't let it burn!")
isWaterNeed = true;
throw "I put the water on the fire anyway.";
)
)
// END - catch (string Ex)
//--------------------------------------------------------
,
//--------------------------------------------------------
// catch(RoofCollapsedException Ex)
RoofCollapsedException
try_code (
protect (
if (RoofCollapsedException.isAnythingWeCanDo == false)
throw new TooLateException(RoofCollapsedException);
else
isWaterNeeded = true;
)
)
// END - catch(RoofCollapsedException Ex)
//--------------------------------------------------------
)
// closing without catchall exception handler
//--------------------------------------------------------
}
Now, I admit, it's ugly. Reeeal ugly. I'm sure there's a better way to write those macros, but as just a theoretical proof-of-concept I don't think there's anything there that doesn't work. But the real solution can't be this hard. The reason it doesn't turn out well isn't that the idea is that ugly. It's just that the macros can't implement it in a clean way. Since it's such a regular pattern there just oughtta be a way to make it happen without even touching your source code. If only the C preprocessor wasn't the only choice...
;) So. Actually I think there may be. A superior solution is to use a more-powerful preprocessor, that gives cleaner C++ code by allowing you to compile even without being additionally preprocessed (e.g. directives as comments). I think that it would be fairly easy to write something up using a tool like CS-Script (which will run under Mono) and I believe some examples are included in the documentation of the 'precompiler' process which lets you do this. And, really, for this: you don't even need directives. Directives would be cool, but you don't need a general purpose macro processor to do what you need. Of course, it goes without saying that you could write it in anything that has the capability to process a text file.
Although I have not tried to implement it yet, I think this could perhaps be just a processor that is run on an entire group of files that require no modifications directly to the code whatsoever. (Find all try/catch
blocks in the file, gather up the types, create the extra statements, and write out the file.) Perhaps move the directory the Makefile pulls the build files from, then prior to compiling, process all the files and put the output in the new build subdirectory. I bet LINQ could do it in a few little statements, although that doesn't mean I can write the LINQ. :) I still bet it wouldn't be that big of a task, amd it would be the perfect way to implement the solution; define the stack, the class, and the static checker functions in the class.
Which reminds me... for "completeness":
bool ExceptionStackHandling::isThereACatchBlock(string Type)
{
return (ExceptionStackHandling.Stack.peekOnType(Type) > 0);
}
So, in closing: I can hardly imagine dealing with code like the code that you end up having with the macros. Now, I didn't indent, and I suppose with that it would become semi-more semi-readable, but the problem has just moved: now if you have seven exception types being handled, you have seven indents pushing everything off the screen. But I do think something can be done with an simple external application that does it all automatically for you.