Topic: exception-handling strategies (Internationalization/localization


Author: "Hillel Y. Sims" <usenet@phatbasset.com>
Date: Fri, 19 Jul 2002 05:52:46 GMT
Raw View
(I am unsure if this belongs in c.l.c++.m at this point, but I'm following
up in the originating group for now.)

"Alexander Terekhov" <terekhov@web.de> wrote in message
news:3D367BF8.19418852@web.de...
>
> "Hillel Y. Sims" wrote:
> [...]
> > Contrary to popular advice, you should never actually do "catch (...)
{}" to
> > achive nothrow code, but you can safely do "catch (std::exception&) {}"
if
> > all exception types are inherited from std::exception.
>
> I guess, you have a rather strange opinion with respect to "nothrow code",
> Hillel. Yes, "nothrow code" isn't meant to throw, but it also
just-can't-FAIL
> [funny destructors with sort-of "error logging" aside] meaning that the
caller
> is given the guarantee that the operation WILL succeed... or the program
will
> "die" in core-/crash-/whatever-dump or simply "JIT" debugger. ;-)


The whole POINT of that "safely achieve nothrow code" was specifically about
the "funny destructors with sort-of error logging" that you are just
throwing aside. I KNOW that you don't just do that randomly everywhere to
achieve "nothrow" status. You don't even need to do that in most destructors
too. Grrr.

I have come to the conclusion that C++ exceptions (eg, anything from a
"throw") should not really be used to indicate fatal error status (such as
to provide core dump / debugging info). At least not for high-reliability
type of software. David Abrahams has explained how forced-termination (with
core dump, etc) would better be represented manually using something like
abort(); or you might have hardware exceptions occur which you can't do
anything about. Let's think about something like logic_error -- if some
piece of code can detect an error condition that causes it to throw
logic_error, then it obviously is avoiding actual corruption of the program
state. Therefore, even if the exception is thrown, the program state is
still valid (even if there is a logic error in there somewhere). It might be
helpful to log the exception information somehow, but there is no absolute
NEED for it to terminate the program, as opposed to hardware exception,
which absolutely MUST terminate the program, which is why catch(...) is bad,
because it can interfere with this on many platforms...

>
> > You cannot correctly
> > handle an exception that you do not know what type it is and cannot even
> > give it a name.
>
> And how do you know "what type it is" if you catch via std::exception or
alike?

Of course you don't (and if you could dynamic_cast it, why not just use an
explicit catch clause...). But at least you know just a LITTLE bit more than
catch(...) gives you -- and that little is a lot! You can know with full
certainty that you are not dealing with a hardware exception! (On many
platforms, hardware exceptions are caught by catch(...); regardless of
whether this is a good thing, this is the reality.) So in certain
circumstances, such as destructors, it may be valid to swallow it and
continue normal execution regardless of the actual dynamic type of the
exception, whereas it is potentially far more dangerous to swallow any
random exception from catch(...).

Imagine we are writing a destructor of some object that must call a cleanup
function in some DLL, whereby we are a) not completely certain that it won't
throw an exception and we don't even know what type and b) we cannot allow
any exceptions to propagate outside the destructor because everyone knows
that is a Bad Thing. So in this case the typical advice has been that you
can use a catch-all & swallow-type thing to prevent any exceptions from
leaving the destructor, because there is no other sensible place for them to
go anyhow. (I am pretty sure I'm not just making this up out of my own
ignorance, I'm sure you've read similar things too.)

Obj::~Obj()
{
   try {
     dll_cleanup_func_that_may_do_whatever_crazy_thing();
   }
   catch (...) { /* swallow it, maybe log some random information, but don't
rethrow */ }
}

Now frankly if you are using some DLL that can throw any random exception
that is not inherited from std::exception, then you're just SOL. Because
using catch(...) is just as, if not more, dangerous to stability here than
allowing random exceptions to propagate, since it may pick up hardware
exceptions too, and you do _not_ want to swallow a hardware exception (at
least not here). If you are unable to change the DLL's behavior because it
is some third-party code that you purchased and all you have is some binary
code, well I guess you're just up against a wall. Try to get the vendor to
fix it. On Windows there is apparently a workaround whereby you can prevent
hardware exceptions from coming through the catch(...). I guess you could
try that, but it's clearly not a portable solution. Then again maybe
portability isn't even an issue for you. I would prefer that people would
start making it a point to always derive their exception objects from
std::exception and not just throw ints or char*s or whatever random
hierarchies. Consider how much more straightforward and portable and simpler
everything is if all the exceptions being used are derived from
std::exception. (Can anyone point out any objections to the notion of
inheriting all exception objects in ordinary user code from std::exception?
Except for certain very special cases which would specifically be intended
to go around this paradigm in the same way as we are trying to let hardware
exceptions pass uninterrupted, all exception objects should be inherited
from std::exception.)

Obj::~Obj()
{
   try { ... }
   catch (std::exception&) { /* swallow it, maybe log some info, don't
rethrow */ }
}

The catch here ;-) is that this relies on all user exceptions being derived
from std::exception in some way. Maybe that's too fragile for some people
(in my case it works just fine, since for our inhouse software we control
the entire code base, and the few-and-far-between third party packages we
use are open-source, so we can fix things up the way it should have been in
the first place as necessary). The alternative is that catch(...) is really
much more fragile/dangerous than a lot of people think anyhow. Besides,
considering all the goodness that comes out of adhering to a strict strategy
of inheriting all exceptions from std::exception (all C++ std library
exceptions already follow this paradigm too), what possible downside is
there to doing this, aside from having to type a few more characters to
define a real class definition instead of throwing an int?

thanks,
hys

--
Hillel Y. Sims
FactSet Research Systems
hsims AT factset.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]