Re: Necessity of multi-level error propogation
James Kanze wrote:
Jeff Schwab <j...@schwabcenter.com> wrote:
James Kanze wrote:
Jeff Schwab <j...@schwabcenter.com> wrote:
It would be my preference to wrap strtol in a function that
throws exceptions on error, to avoid the need for little
if-statements everywhere it is called.
I don't know. I think in this case, a lot of the time, you
could (and probably would want to) handle the error
immediately in the calling code.
Then the caller can use a try/catch block. That isn't much
more syntax than an if-statement,
Oh yes it is, since it implies creating a scope for the call
(and not just for the error handling).
Not so; the call already has its own scope. If you find yourself with a
try/catch in the middle of a function body, it's probably time to refactor.
Which in turn may mean
either moving the error handling down further in the function,
or declaring the variable before initializing it.
Neither of those is implied, either. Remember, we're replacing an
explicit clear of errno before the function call, and a check afterward.
The non-exception code would have to look something like this:
long parse(std::string s, long default_, int base) {
typedef char** end_pointer;
errno = 0;
long const value = strtol(s.data(), end_pointer( ), base);
if (errno) {
// log the error, or do other special-case handling...
return default_;
}
return value;
}
Yuck. If my::strtol instead throws an exception, we can write:
long parse(std::string s, int base, long default_) try {
typedef char** end_pointer;
return my::strtol(s.data(), end_pointer( ), base);
} catch (std::exception const&) {
// log the error, or do other special-case handling...
return default_;
}
We've added zero scopes, moved the error-handling out of the main logic,
and avoided the need for the intermediate "value" variable. We've
also replaced direct use of the macro errno with use of an exception type.
it allows the compiler to favor the case in which an error
does not happen,
It can do this in any case.
It could, if it knew which case to favor. GCC has extensions to let you
specify which case should be favored, but I'm not aware of anything in
standard C++ that serves the same purpose.
and it keeps the error-handling code separate from the main
logic.
Which isn't necessarily an advantage, for errors which have to
be handled immediately.
I disagree. If it belongs in the main logic, then by definition, it's
part of that logic, and we're no longer discussing error-handling. If
it's semantically separate from normal operation, then it ought to be
lexically separate, as well.
I don't see any reason at all to prefer cursing a particular
return value; that seems more like dark magic to me.
Furthermore, if the caller forgets to check the return code
for its error value, a silent bug is likely to ensue, whereas
an exception will make itself heard unless explicitly
squashed.
Fallible aborts if you access the return value when the status
is invalide. Fallible supports some syntactic sugar as well,
e.g.: getSomething().elseDefaultTo( defaultValue ). And it's no
more dark magic than exceptions---less, really.
If you're using Fallible, then you haven't got a magical return value;
you've got a static type that captures logical intent. That's a far cry
from standard math functions forcing you to check errno. Anyway, what
you're saying is that the error only occurs if the value is accessed; in
other words, you're not counting on the client to remember to check a
value. That's all fine, and would be a completely different discussion.
The C standard library functions don't work that way. (I'd still
prefer an exception to an abort, but I really don't want us all to
tumble back down that rabbit hole just now.)
To me, strtol also seems to be a very low-level function, and
doesn't seem likely to know what to do on failure, anyway. At
the time strtol was written, of course, the situation probably
was different.
Even today, maybe. But I think the most frequent use would be
in the implementation of the operator>> in istream, or other low
level code of this sort. But at this point, we're so low that
you'll almost want to handle the error immediately, in order to
map it to the class specific error reporting mechanism.
Yes.
So an exception really isn't appropriate. In a new design,
I'd probably use Fallible.
To me, that means: "This isn't really an error. It's
something I expect to happen in the course of normal
operation,
That's also true. Although that's not really my argument. But
I don't think you can consider any format error in input
"exceptional". Just the opposite, you're almost certain to see
it from time to time.
Who said anything about "any format error in input?" Anyway, as long as
we're calling it an error, I still think it ought to have a
corresponding exception. Despite some of the older wisdom, I'm not
convinced that "exceptions" should only be for really exceptional
situations. IMO, they're best reserved for errors, and used consistently.
and I want to be able to check for it at my leisure."
That's not Fallible;
Really? So what was that elseDefaultTo method you just showed?
that's the idiom used by std::istream
By default.
It's also useful---in the case of output
(std::ostream) and floating point, it's probably the preferred
idiom.
"The" preferred idiom. Wow.
(Typically, I'll output an entire file, and only check
the status and generate an error return after close.)
Once the first error occurs, are subsquent operations guaranteed not to
clear it, or to otherwise do any harm?
I don't see anything inherently wrong with that, but it really
changes the discussion. (I'd still prefer an exception for
strtol, but I could see the Fallible PoV.)
In the case of strtol, there's another, clinching argument
against exceptions: the function has to be usable from C:-).
And since we can't break existing code, it has to use errno:-(.
Nobody suggested changing the existing functions.
But note that the function already has a number of "return
values": the error code, the results of the conversion, and
the new end pointer. Globally, I think std::istream handles
the case a lot cleaner.
Yes, at least insofar as it lets the client dictate what's an
error. To me, EOF isn't (necessarily) an error, and I don't
want an exception when I hit it; by contrast, failure to read
an integer where one is expected does constitute such an
error.
That's where we disagree. An error in input format is really to
be expected, at least in most cases. What I generally don't
expect (and would consider exceptional, and report by an
exception) might be a hardware read error (except, perhaps, in
very, very low level code, e.g. an implementation of a level 2
or a level 3 protocol, like LAP-D or IP).
Fair enough. The disagreement may be more terminological than technical.