Why do some code bases don't use exceptions?

J

James Kanze

James said:
"dragan" ha scritto nel
messaggionews:[email protected]...
[...]
12. they not have "the written form"
"if(a/b > c) u=z;"
if "a/b" throw one exception
in the plain text above;
there is not a single char in
"if(a/b > c) u=z;"
that show this chanches
That's probably because there is no such chance:).
But seriously, this is the one real problem with exceptions. And
the possibility of exceptions does require some additional
thought. But the problem has been addressed, and we do know
what has to be done.
13. the "exception header" not "know" the point of code
where is the problem. Instead returning the error-result of the
function the program know where is the error
That's the whole point of exceptions. They're for the sort of
problems where it doesn't matter where the problem occured; the
handling is the same. If you run out of memory processing a
request, for example, it doesn't matter where you were in the
processing when you ran out; you just abort the request (but not
the process) with an error message, and continue.
You seem to be big on aborting.

Attention about word use. There's aborting (in the sense of
calling abort()), and aborting (in the more general sense, of
immediately terminating some action that you were in the process
of doing). In this case, I'm using the other sense: say you've
received a request on your LDAP server, and you run out of
memory trying to service it (because it requires interpreting
some obscenely complicated filter expression, for example).
When you detect the lack of memory, you're down in some really
deap parsing function, executing operator new. You (or rather
the system) raises an std::bad_alloc exception, which you catch
at the top level, and abort the request (not the program),
returning an error message of "insufficient resources", or the
like.
A very valid pattern is fixing the problem and resuming.

If that's a valid reaction to the error, aborting isn't the
answer. If the error was a coding error, you can't fix the
source, recompile the code, and resume, so you abort. If the
error was insufficient memory due to an overly complicated
request, you can't simplify the request and resume: you abort
the operation. (On receiving an "insufficient resources" error,
of course, the client may try a simpler request.)
Some version of Windows expands the stack that way: a
violation occurs when trying to push beyond the current stack
frame and the system catches the error, expands the stack by
4k and continues processing.

(Isn't that how most systems work? That's more or less the way
the old Berkley kernel worked on Sun 3's, and IIRC, PDP-11's
memory manager unit was designed expressedly to support
something like this, back in the days before virtual memory.)

I'm not too sure how that's relevant to user code, however. Are
you saying that if you get std::bad_alloc, you should try to get
more memory?

Frankly, out of memory is a special case, and most of the
programs I've worked on installed a new_handler to abort in such
cases. Not all, however, and particularly on transaction based
systems where transactions can require a lot of memory, it often
makes sense to just abort the transation. I can't think of much
else you could do: call some system routine to create more
virtual memory? That often requires priviledged access. And
if the insufficient memory was because you'd used up your
address space, rather than because the system didn't have any
more memory to give you, it won't help anyway. And you can do
it from the new_handler (which provides resumption semantics).
 
B

Brian

Attention about word use.  There's aborting (in the sense of
calling abort()), and aborting (in the more general sense, of
immediately terminating some action that you were in the process
of doing).  In this case, I'm using the other sense: say you've
received a request on your LDAP server, and you run out of
memory trying to service it (because it requires interpreting
some obscenely complicated filter expression, for example).
When you detect the lack of memory, you're down in some really
deap parsing function, executing operator new.  You (or rather
the system) raises an std::bad_alloc exception, which you catch
at the top level, and abort the request (not the program),
returning an error message of "insufficient resources", or the
like.

Thanks for the clarification. I wasn't sure exactly what you
meant in prior posts and was thinking about asking.
If that's a valid reaction to the error, aborting isn't the
answer.  If the error was a coding error, you can't fix the
source, recompile the code, and resume, so you abort.
Agreed.

 If the
error was insufficient memory due to an overly complicated
request, you can't simplify the request and resume: you abort
the operation.  (On receiving an "insufficient resources" error,
of course, the client may try a simpler request.)

Previously I've suggested adding a mechanism to the language
to support preventing an exception from causing base classes
from being destroyed if the exception occurs in, for example,
the most derived class. I thought about it in baseball terms
-- a single is often still useful even though not as good as
a double or more. In a marshalling context, a lot of work
has been done by another computer, the network and the
computer that is receiving the object. It makes sense to
me to have a way to preserve that work. (I'm not saying
that the default behaviour should be changed, but that a
way to get this behaviour should be added.) I also thought
of this in terms of a conversation between two people --
if you hear 90% of what someone tell you, you try to tell
them where you lost them and ask them to just repeat a
fraction of the original statement.


Brian Wood
http://webEbenezer.net
 
D

dragan

James said:
So we agree. (On that, at least.)



When you detect a coding error (a violation of a precondition,
for example), aborting is *usually* the most appropriate
reaction. Not aborting should only be done in explicit cases,
where you understand the issues and are prepared to cope with
them.



Abort is generally the most effective means fo switching to the
backup system. It's the generally accepted means of reacting to
software errors. More generally, if a program is doing the
wrong thing, it generally should be stopped as quickly as
possible, so that it doesn't do more of the wrong thing.



Error propagation, error reporting: same thing, really.

Completely different. Reporting = things like displaying an error dialogbox,
writing to an event log, sending a backtrace to the admin or developer...

Propogation is just that: propogating an error from the detection point to
the handling point. That may be as simple as returning an error code back up
the call stack or as not-simple as jumping to the handler with an error
object off some kind via an exception mechanism.

Reporting is a "handling" strategy. Propogation is more just about
mechanisms.
Propagation is probably the better word, though; it just slipped
my mind.


I think we're talking about different things. I was talking
about "reporting" the error to the location in the code where it
would be handled.

That isn't reporting. It's propogation. You are using terminology
incorrectly and that is sure to confuse.
Propagation is reporting, at least in the sense I was using the
word.

That is incorrect.
Categorization is probably part of detection.

No. Perhaps "classification" is a better word for clarification.
You can certainly define it a lot finer, and there's often a lot
in each of those words.

It's not a question of more granularity. The issue at hand is recognizing
the larger scope of error management vs. just a subset of the technical
constituents of it.
My basic idea was that you have to
think of errors in three contexts: where you're going to detect
the error (e.g. how, what type of error, etc.), where you're
going to treat it (log it, retry, etc.), and in between, where
you have to propagate it from where it was detected to where it
is to be handled.


At the coding level:).

Not even at the coding level. To say detect/handle/report is an error of
omission even at the coding level.
I totally agree, my categorization
doesn't cover things like documentation and communication (to
other programmers), and they are very important.



Two words, same thing.

Not the same at all. Orthogonal.
Yes, but having to write a try block, then a catch statement, is
more complicated than an if, if you want to process the error
immediately. You can use an exception, but it's about like
pealing a grape with a butcher's cleaver.

To say "they are of no use at all" is incorrect. Someone may prefer that
style (those who think one mechanism only is nirvana comes to mind).
 
R

Richard

[Please do not mail me a copy of your followup]

"Alf P. Steinbach" <[email protected]> spake the secret code
* James Kanze:

Off the cuff,

#define CATCHX( result, f, args ) \
do { \
try { \
result = f args; \
} catch( std::exception const& x ) { \
result.set_x( x ); \
} \
} while( false )

.....which is a good example of why macros are evil.
 
A

Alf P. Steinbach

* Richard:
[Please do not mail me a copy of your followup]

"Alf P. Steinbach" <[email protected]> spake the secret code
* James Kanze:
Off the cuff,

#define CATCHX( result, f, args ) \
do { \
try { \
result = f args; \
} catch( std::exception const& x ) { \
result.set_x( x ); \
} \
} while( false )

....which is a good example of why macros are evil.

Mostly macros are evil, but your comment for this case makes no sense to me.

Would you care to elaborate a bit on exactly what you mean (if anything).


Cheers,

- Alf
 
D

dragan

peter said:
Not any exact ones, but I have been working in places where exceptions
were not used (due to some code being quite old and no one caring
about fixing). Code there typically looked something like:

int func(parm p,std::string &result)
{
std::string s;
int error;

result = func_1(parm,s);
if (error != OK)
{
return error;
}
error = func_2(s);
if (error != OK)
{
return error;
}
result = s;
return OK;
}

#define ERROR (-1)
#define OK (0)

#define Try(x) if(x != OK) goto unwind;
#define CatchAll unwind:

// Not the same function as you have cuz I
// don't want to figure out what you were
// doing hypothetically. Adequate for illustration.
// Many variations on the below theme exist
// including masking of setjmp/longjmp etc.
//
int func (parm p)
{
std::string s;
Try(func_1(p, s))
Try(func_2(s))
return OK;

CatchAll
// do cleanup, rollback, recovery, etc. as appropriate
return ERROR;
}

Macros are your friend to make the language conform to YOUR preferences! :)
 
D

dragan

James said:
James said:
"dragan" ha scritto nel
messaggionews:[email protected]... [...]
12. they not have "the written form"
"if(a/b > c) u=z;"
if "a/b" throw one exception
in the plain text above;
there is not a single char in
"if(a/b > c) u=z;"
that show this chanches
That's probably because there is no such chance:).
But seriously, this is the one real problem with exceptions. And
the possibility of exceptions does require some additional
thought. But the problem has been addressed, and we do know
what has to be done.
13. the "exception header" not "know" the point of code
where is the problem. Instead returning the error-result of the
function the program know where is the error
That's the whole point of exceptions. They're for the sort of
problems where it doesn't matter where the problem occured; the
handling is the same. If you run out of memory processing a
request, for example, it doesn't matter where you were in the
processing when you ran out; you just abort the request (but not
the process) with an error message, and continue.
You seem to be big on aborting.

Attention about word use. There's aborting (in the sense of
calling abort()), and aborting (in the more general sense, of
immediately terminating some action that you were in the process
of doing). In this case, I'm using the other sense: say you've
received a request on your LDAP server, and you run out of
memory trying to service it (because it requires interpreting
some obscenely complicated filter expression, for example).
When you detect the lack of memory, you're down in some really
deap parsing function, executing operator new. You (or rather
the system) raises an std::bad_alloc exception, which you catch
at the top level, and abort the request (not the program),
returning an error message of "insufficient resources", or the
like.

Ah. Bad choice of word then to describe that. I think most people who see
"abort" think exit the process or terminate the thread. Even in your example
above though, I wouldn't expect abort to occur unless the situation has been
going on for some time. The server should have preallocated some resources
for use in such situations.
If that's a valid reaction to the error, aborting isn't the
answer. If the error was a coding error, you can't fix the
source, recompile the code, and resume, so you abort. If the
error was insufficient memory due to an overly complicated
request, you can't simplify the request and resume: you abort
the operation. (On receiving an "insufficient resources" error,
of course, the client may try a simpler request.)


(Isn't that how most systems work?

I don't know.
That's more or less the way
the old Berkley kernel worked on Sun 3's, and IIRC, PDP-11's
memory manager unit was designed expressedly to support
something like this, back in the days before virtual memory.)

I'm not too sure how that's relevant to user code, however. Are
you saying that if you get std::bad_alloc, you should try to get
more memory?

See my comment above.
Frankly, out of memory is a special case, and most of the
programs I've worked on installed a new_handler to abort in such
cases. Not all, however, and particularly on transaction based
systems where transactions can require a lot of memory, it often
makes sense to just abort the transation. I can't think of much
else you could do: call some system routine to create more
virtual memory?

Preallocate for use in those times. Could be from an entirely different heap
or something. Or hardware even: a mem card in a PCI-X slot ... possibilities
are endless. EH is application-specific.
 
R

Richard

[Please do not mail me a copy of your followup]

"Alf P. Steinbach" <[email protected]> spake the secret code
* Richard:
[Please do not mail me a copy of your followup]

"Alf P. Steinbach" <[email protected]> spake the secret code
* James Kanze:
Yes, but having to write a try block, then a catch statement, is
more complicated than an if, if you want to process the error
immediately. You can use an exception, but it's about like
pealing a grape with a butcher's cleaver.
Off the cuff,

#define CATCHX( result, f, args ) \
do { \
try { \
result = f args; \
} catch( std::exception const& x ) { \
result.set_x( x ); \
} \
} while( false )

....which is a good example of why macros are evil.

Mostly macros are evil, but your comment for this case makes no sense to me.

Would you care to elaborate a bit on exactly what you mean (if anything).

Macros hiding loops, returns or try/catch blocks are evil IMO.
 
A

Alf P. Steinbach

* Richard:
[Please do not mail me a copy of your followup]

"Alf P. Steinbach" <[email protected]> spake the secret code
* Richard:
[Please do not mail me a copy of your followup]

"Alf P. Steinbach" <[email protected]> spake the secret code
<[email protected]> thusly:

* James Kanze:
Yes, but having to write a try block, then a catch statement, is
more complicated than an if, if you want to process the error
immediately. You can use an exception, but it's about like
pealing a grape with a butcher's cleaver.
Off the cuff,

#define CATCHX( result, f, args ) \
do { \
try { \
result = f args; \
} catch( std::exception const& x ) { \
result.set_x( x ); \
} \
} while( false )
....which is a good example of why macros are evil.
Mostly macros are evil, but your comment for this case makes no sense to me.

Would you care to elaborate a bit on exactly what you mean (if anything).

Macros hiding loops, returns or try/catch blocks are evil IMO.

OK.

Then observe:

* The macro doesn't hide a try-catch block. Its name makes it abundantly
clear that it catches an exception.

* The macro doesn't contain a return.

* The macro doesn't contain a loop. Or well, there is what might look like
a loop, but it's just an old well-known syntactical device, to make the
macro invocation function-like wrt. C++ semicolon rules, especially in
if-else constructions.If you're unfamiliar with this then you've learned
something new! :)

So, none of your evilness indicators are present.


Cheers & hth.,

- Alf


PS: By the way, much of this macro stuff will be unnecessary in C++0x, which
much better supports argument forwarding.
 
R

Richard

[Please do not mail me a copy of your followup]

We can agree to disagree, but I don't like your macro.
 
V

Vladimir Jovic

dragan said:
#define ERROR (-1)
#define OK (0)

#define Try(x) if(x != OK) goto unwind;
#define CatchAll unwind:

// Not the same function as you have cuz I
// don't want to figure out what you were
// doing hypothetically. Adequate for illustration.
// Many variations on the below theme exist
// including masking of setjmp/longjmp etc.
//
int func (parm p)
{
std::string s;
Try(func_1(p, s))
Try(func_2(s))
return OK;

CatchAll
// do cleanup, rollback, recovery, etc. as appropriate
return ERROR;
}

A function (or a method) has to have the same crap. This remind more of
C, then C++.

btw take a look at this:
http://www.google.com/search?q=macros+are+evil&btnG=Search+the+C+++FAQ&sitesearch=www.parashift.com

Once you really try exceptions, you will laugh at this example.
 
V

Vladimir Jovic

Vladimir said:
A function (or a method) has to have the same crap. This remind more of
C, then C++.

Off course, this should have been:

A function (or a method) *that is using this function* has to have the
same crap. This remind more of C, then C++.
 
P

peter koch

#define ERROR (-1)
#define OK (0)

#define Try(x) if(x != OK) goto unwind;
#define CatchAll unwind:

// Not the same function as you have cuz I
// don't want to figure out what you were
// doing hypothetically. Adequate for illustration.
// Many variations on the below theme exist
// including masking of setjmp/longjmp etc.
//
int func (parm p)
{
    std::string s;
    Try(func_1(p, s))
    Try(func_2(s))
    return OK;

    CatchAll
    // do cleanup, rollback, recovery, etc. as appropriate
    return ERROR;

}

Macros are your  friend to make the language conform to YOUR preferences! :)

You just gave a perfect example of how to make bad code worse. Your
code has several issues:

1) You fail to "return" the result when everything goes well.
2) You fail to return the error-code when things go wrong.
3) Your code has reintroduced the hidden paths you dislike about
exceptions.

All this for reducing a function from ten lines to 6 (generously not
counting your macros) compared to the exception-enabled oneliner.

Finally, your system is quite fragile - forbidding its use in some
cases. Change the function slightly, and it becomes ill-formed:

int func (parm p)
{
std::string s1;
Try(func_1(p, s1)) // Ill-formed!
std::string s2;
Try(func_2(s2))
return OK;


CatchAll
// do cleanup, rollback, recovery, etc. as appropriate
return ERROR;
}

So your macro-voodoo not only introduces hidden return-paths. It also
prevents writing idiomatic C++ (if that was not already lost) and
increases the code-size considerably.

/Peter

/Peter
 
J

James Kanze

I guess you could always go down the Java-like route of
introducing a checked_exception base class for exceptions
which can be checked in exception specifications and assuming
that where legacy code does throw, it throws only unchecked
exceptions. Not sure this would make things any easier though?

It seems to combine the disadvantages of both. In practice,
most of Java's checked exceptions are things that logically
should be a return code: things that usually have to be handled
locally, by the calling function. At present, C++ does it
better (although IMHO, compile time checking would be better
still, if it weren't for the problem of legacy code): the most
useful guarantee (perhaps the only useful guarantee) is that a
function doesn't exit via an exception, ever. You can't express
this guarantee in Java.
Just to clarify: do you think it doesn't need a try block, or
that that's not a problem? (FWIW, my current answers to those
would be that I think it does need a try block, but that I
don't think that's a major problem - but I'm open to
thoughts.)

I don't think that it would be a problem requiring the try
block. I'm really in favor of static checking. But I've
definitely heard the above argument. (Note that some early
implementations of exceptions and try blocks had some runtime
costs, which might not be acceptable in such a function. I
don't think that this would be an issue with most modern
compilers, however.)
 
J

James Kanze

[....]
Ah. Bad choice of word then to describe that.

I didn't invent the language (English). I just use it. I don't
know of any other word for what I described; abort is the
standard word.
I think most people who see "abort" think exit the process or
terminate the thread.

I think most people read more than just a word at a time, and
the expression "abort the request" is quite clear. "Abort the
request" is not "abort the process".
Even in your example above though, I wouldn't expect abort to
occur unless the situation has been going on for some time.
The server should have preallocated some resources for use in
such situations.

I'm not sure I follow. The server will certainly have some
resources preallocated, so that it can successfully log the
error, unwind the stack, and return an error message. But by
definition, it can't use those resources for parsing the
request, or they won't be available for the things they were
reserved for.
I don't know.
See my comment above.

Which one? Are you saying that if you get std::bad_alloc, you
should output a message to the terminal, asking the sysop to
insert additional memory chips, and only continue when he does?
Preallocate for use in those times. Could be from an entirely
different heap or something. Or hardware even: a mem card in a
PCI-X slot ... possibilities are endless. EH is
application-specific.

Preallocate what? Parsing an LDAP request requires unbounded
memory, since the request can be arbitrarily complicated: the
filter expression can contain any number of parenthesized
sub-expressions. It's just like a C or a C++ compiler: feed it
a file with a function which contains 2 billion nested
parentheses, and see what happens. You can't predict up front
how complex an expression will be, and how much memory you'll
need. Either you arbitrarily limit the complexity (for example,
to prevent stack overflow if you're using recursive descent), or
you handle insufficient memory. Or both: you might normally be
able to handle the expression, but not if a thousand or so
clients send it at the same time.
 
D

dragan

Paavo said:
If you are happy writing the above two lines, it shows clearly IMO
that you have never done any large scale development in C++.
Consequence: you have no idea what you are talking about when ranting
about exceptions. No offense, I just want to put things in a more
correct perspective.

That you thought that I proposed that as an alternative to anything shows
that you are a greenbean, wet behind the ears. :p I'm not here to divulge
proprietary or patentable technology. Deal with it little boy. Cry me a
river because I won't give you my source code. BTW, your mommie called me to
tell you to: GET A CLUE!
 
D

dragan

Paavo said:
If you are happy writing the above two lines, it shows clearly IMO
that you have never done any large scale development in C++.
Consequence: you have no idea what you are talking about when ranting
about exceptions. No offense, I just want to put things in a more
correct perspective.

That you thought that I proposed that as an alternative to anything shows
that you are a greenbean, wet behind the ears. :p I'm not here to divulge
proprietary or patentable technology. Deal with it little boy. Cry me a
river because I won't give you my source code. BTW, your mommie called me to
tell you to: GET A CLUE!
 
D

dragan

Vladimir said:
A function (or a method) has to have the same crap. This remind more
of C, then C++.

btw take a look at this:
http://www.google.com/search?q=macros+are+evil&btnG=Search+the+C+++FAQ&sitesearch=www.parashift.com

Once you really try exceptions, you will laugh at this example.

Once you study the realm that C++ exceptions fall under, you will be
enlightened. It's fine and dandy to just accept things thrown at you in the
form that they are thrown at you. If you are "that kind" though, I wouldn't
want you on my development "team". (You betcha I knew people where going to
drive their cadillacs into that "example" I gave. Have you been "had"?)
 
D

dragan

Vladimir said:
Off course, this should have been:

A function (or a method) *that is using this function* has to have the
same crap. This remind more of C, then C++.

"you have some redeeming quality" in that you recognize history. :/ <-- can
anyone parse this?
 
D

dragan

peter said:
You just gave a perfect example

It wasn't an example.
of how to make bad code worse.

Or how to make people THINK? I mean, you're thinking about it, right?
Success.
Your
code has several issues:

1) You fail to "return" the result when everything goes well.

?? You can't see the "return OK;" statement??
2) You fail to return the error-code when things go wrong.

?? You can't see the "return ERROR;" statement?

You "got me": I had to stick in the baggage from the previous poster's
example: std::string. Touche, and: :p. If you were REALLY smart though,
you'd ask, "why is this dude who is avoiding exceptions using
std::string??!". Answer: he wouldn't.
3) Your code has reintroduced the hidden paths you dislike about
exceptions.

All this for reducing a function from ten lines to 6 (generously not
counting your macros) compared to the exception-enabled oneliner.

Finally, your system is quite fragile - forbidding its use in some
cases. Change the function slightly, and it becomes ill-formed:

int func (parm p)
{
std::string s1;
Try(func_1(p, s1)) // Ill-formed!
std::string s2;
Try(func_2(s2))
return OK;


CatchAll
// do cleanup, rollback, recovery, etc. as appropriate
return ERROR;
}

So your macro-voodoo not only introduces hidden return-paths. It also
prevents writing idiomatic C++ (if that was not already lost) and
increases the code-size considerably.

If you like Humvees, but they only were available in pink, maybe you'd
repaint it? Some people would just buy something else. Oh, there IS NOTHING
ELSE you say? Don't be so sure. Is that a corporate Vee you're driving?
Dude, nice COLOR! (hehehe).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
473,792
Messages
2,569,639
Members
45,353
Latest member
RogerDoger

Latest Threads

Top