J
James Kanze
To the level of favoring a particular branch of an if/else
statement? Could you please provide some links? That sounds
useful, but I've never seen that kind of automated feedback
from the profiler to the compiler.
It should be documented in the compiler documentation. For Sun
CC, see the -xprofile option, for example, see the section
"Advanced compiler options: Profile feedback" in
http://developers.sun.com/solaris/articles/options.html, for a
quick survey. The linker for VC++ has a /PROFILE, although I'm
not very familiar with it.
No, I'm saying that every error should have a corresponding
exception, and every exception should represent some error.
In other words, you're defining exception as error, and error as
exception.
The reason we call it an error is that it's an error, and
that's the same reason we (well, I) throw an exception.
But that really doesn't advance us much. What is an error, and
what isn't? And surely you won't dispute that there are
different types of errors: dereferencing a null pointer is not
the same thing as a format error in an input file; for that
matter, a format error in an input file that the program itself
wrote earlier is not the same thing as a format error in a
configuration file, neither of which are the same thing as a
format error in interactive input.
If all of these are to be called "errors", they we definitely
have a case where one size does NOT fit all.
I'm not trying to change any definitions; I'm trying to stick
with definitions that are consistent and meaningful.
Yes, or that exceptions aren't always the best response to
errors. It seems to me that you started with the latter
point, but have drifted to the former.
In the terminology I use, many different things are considered
"errors". If my compiler core dumps, it's an error, and if my
C++ code which its compiling is missing a semicolon, it's an
error, but I certainly don't expect the compiler to treat the
two in the same fashion.
But you call elseDefaultTo (or whatever other interface
functions Fallible supports) at your leisure. That's in sharp
contrast to exceptions; they demand attention, unless
explicitly squashed.
OK. I understand. You mean that I can keep the error status
around, and test it later. That's true to a point: what I can't
do with Fallible is use the return value if the error status
wasn't OK. The usual use of Fallible (and return codes in
general) is to check the error status immediately (or very soon)
after calling the function. When I read "check at your
leisure", I imagined something more like ostream or IEEE
floating point, where you can continue processing as if the
error hadn't occured, only checking it at some later state.
Here's an example of conceptually similar case, combining
exceptions and a Fallible-like approach. I recently wrote
some code to handle exceptions that occur in asynchronous
worker threads. The exception is caught in the thread where
it occurs, and info about it is stuffed into a variable. If
the main thread ever tries to retrieve the product of the
worker thread, an exception is thrown in the main thread. If,
on the other hand, the main thread never attempts to retrieve
the worker thread's product, then the main thread will never
even know the exception occurred. The meaning, given a
consistent relationship between exceptions and errors, is that
errors in worker threads do not necessarily translate to
errors in the main thread; i.e., the "erroneousness" of a
given piece of code depends on the point of view. This is
important for thread-level concurrency, because it allows
speculative execution of code that might turn out to be
meaningless, or even self-contradictory.
Sounds reasonable to me.
Modern processors use similar tricks, e.g. non-faulting loads.
Interestingly, I just noticed that IA64 associates an extra
bit (called the "Nat" bit for some reason) with each datum,
indicating whether a non-faulting load "would have" faulted.
The bit is automatically propagated through arithmetic
calculations, so you can just let the whole sequence proceed,
to be checked if and when you so require. This seems
analogous to Fallible, to your preferred ostream use, and to
my opinion that erroneousness is in the eye of the beholder.
Fallible and my preferred ostream use *are* distinct. Fallible
is designed to be checked immediately, and normally is---it is
for functions which return a value (but sometimes can't). And
you normally stop trying on the first error. The ostream use
(and IEEE floating point) is just to continue on, as if nothing
had happened, until the end, and then check once and for all.
Note that the typical ostream usage is a case where you *cannot*
use exceptions. Because of internal buffering, the error may
not show up until close, and if another error occurs, triggering
an exception, close will be called from a destructor, leading to
termination. My usual idiom (wrapped in an OutputFile class
which derives from ofstream) is to have a commit function which
does the close. If this fails, or if the destructor is called
before commit, the class deletes the file it was writing, to
prevent users from accidentally using the incomplete file.
That certainly suggests that the intent was to support your
favored use model of checking once, at the end, rather than
after each operation. I had not previously heard of that.
Do you really check the status after each output? E.i. you
never do anything like:
std::cout << "label = " << someData << std::endl ;
without having activated exceptions for errors?
Note that output errors *are* generally sufficiently serious to
warrent exceptions. The idiom I use was born before exceptions
were available, but since it works, I haven't bothered "fixing"
it. And the fact that output is often desirable in a destructor
makes me hesitant about switching to exceptions. The advantage
to the "check once at the end" model is that if you encounter
some other error, which means that what you're writing is
irrelevant anyway, you can (and will) skip the error checking
completely---in those cases, you can simply let the close in the
destructor do its job, and not worry about it.
Uh oh, we're getting dangerously close to sanity. If we're
not careful, people might get the idea that it's OK for
reasonable people to disagree.
Only if they're not disagreeing with me.
Seriously, I think that there are a number of different
solutions for error reporting. I use at least four in my
applications: return codes, deferred checking (the iostream/IEEE
floating point model), exceptions, and assertion failures. For
any given error, which one is most appropriate is a judgement
call, and the fact that you choose a different one than I would
isn't necessarily wrong. Refusing to consider all of the
possibilities, and insisting that everything from derferencing a
null pointer to a trivial and recoverable input error should be
handled the same way, is. Refusing to consider any one of the
possibilities (e.g. exceptions, which started this thread) is
also wrong, although I can easily imagine that there are certain
applications where one or more of the techniques never actually
finds concrete use.