Re: Error codes vs. exceptions
In article <4fcbeaf4$0$15709$c3e8da3$fdf4f6af@news.astraweb.com>,
Pavel <pauldontspamtolk@removeyourself.dontspam.yahoo> wrote:
Adam Skutt wrote:
On Jun 1, 9:44 pm, Pavel
<pauldontspamt...@removeyourself.dontspam.yahoo> wrote:
Stefan Ram wrote:
Pavel<pauldontspamt...@removeyourself.dontspam.yahoo> writes:
I know you have already received lots of advice; but I think they omit an
important factor, namely whether you are writing a library. In my experience, I
found it quite annoying when the API of a library I use can throw.
And that leads to an answer to the OP's question: When in Rome do as the
Romans do!
Surely, write a catch block?
Oh, sure. For example, if your error processing policy requires assigning
severity level to every error before logging it, you will surely be happy to
write a catch block where the severity is best known to be assigned, won't you?
Now think where in the code this place could possibly be. A little hint:
"generally" it's not in main(), regardless of whether you are handling
std::exception or other exception (unless your app/library is so small that you
"generally" call functions from other libraries in main()).
I think your problem is that you are implying and assuming that the
the best place to handle the error (assign a severity value, etc.) is
in the immediate caller.
You may be correct that it is not "generally" in main but I disagree
with you implication that it is "generally" in the immediate caller.
With exception: you can catch and handle the error where appropriate
With return status code: you must check for the error in the immediate
caller even if you are unable to du anything about it. If it is the
case, you must then anreturn another error and so on.
a. It won't be called at the first place, because a specific exception type
won't be caught.
Generally spkeaing, catch statements should be "rare" (as in not
around every single function call). Where you catch, you may want to
catch probably one of:
- all the exceptions types you know how to handle/fix
- all exceptions (with multiple catch) and handle or log all.
Re-throw if desireable.
b. Even if it were somehow auto-magically called, it often would not have enough
data to fill up the logging function's arguments or the dialogue fields without
cracking the exception or error code.
What about:
catch(...) { log << "Critical Fatal Total Error: unknown exception. We
don't know how and why it has happen so PANIC! is the best answer!"; }
This sounds reasonable to me. If some error occured and you don't
even know that such an error is possible, then you really should
consider it as critical.
It is instructive that in some libraries designed for critical applications by
serious ISPs (e.g. IBM's MQ Series C API), the error codes were returned in
output parameters. A programmer using such API does not have a chance to fully
ignore error conditions because s/he needs to define a variable (sometimes more
than one) to hold error codes. IBM's APIs are a good example because no other
company has had more experience with maintaining huge enterprise-level
applications depending on multiple libraries. It's also instructive that some
'newer-style' and best-usable POSIX C API calls have been re-designed same way
(think of strtol() that all but replaced atol()).
I don't consider design choice for a pure C API where exceptions don't
exist as particularly relevant to a discussion on C++ error handling
which has built in exceptions and where exception is the default
language behaviour.
The above paragraph is not talking about exceptions but its subject is directly
related to the topic. Namely, if we arrange APIs error exposure design decisions
in the order from less to more prominent manifestations of possible error
condition by API functions, our order will look approximately as follows:
This is totally backward:
1. errno, GetLastError(), global variables, C++ functions without exception
specifications that throw and the likes. A programmer is not reminded about
possible error conditions in any way except possibly for the documentation.
Are you seriously grouping exceptions with errno? This is hard to believe.
2. C++ functions with exception specifications. A programmer is reminded about
possible error conditions if s/he has function prototype in front of him or her
while coding the call.
3. Error return codes, A programmer is reminded about error condition as in #2
plus they may be able to turn on some "unused function return value" compiler
warning or the like.
4. Errors are encoded in output parameters of API. A programmer cannot
unwillingly ignore an error condition.
This is so back to front. Exceptions absolutely can't be ignored
passively. This is one of the strongest quality of exception above
either return code or output parameters.
Error ouput parameter are easy to ignore: you just don't test them
after the function call.
One of the big problem with return code, errno or even error output
param is that nothing in the code indicates that the programmer made
the deliberate choice of ignoring the error rather than just being
lazy.
With expection, the error will *always* interrupt normal processing
*unless* the programmer explicitely write code to resume normal
processing. This is a *good* thing.
The corrected list above is:
1- errno, GetLastError(), global variable: programmer need to test
something that is outside current scope and location. Programmer needs
to explicitely call a method or read a variable that is unrelated to
current processing in order to check if an error has occured.
2- return code: very easy to ignore or forget about. How often have
you seen the return value of printf() being ignored? Error checking
needs to be done manually.
3- error output parameter: because you have to explicitely define the
paramter, the programmer is less likely forget and compiler flags may
raise a warning.
4- Exceptions: programmer can't ignore them passively. The only way
to ignore exceptions is to explicitely catch them all and
continue. Error checking is automatic.
The code to capture error codes is simpler,
shorter and faster than that needed to capture exceptions.
I don't see how it can ever be less code than exception handling[4].
- It is simpler because error codes are easier tabulated for their
classification or another mapping (potentially driven by the rules that are
configured externally to the program and that are to be applied to programs in
other languages than C++) to the entities required by the application error
handling policy.
- it is shorter (especially with error-code-based or errno-based approach to
error propagation) because well-encapsulated error-checking code is notably
shorter than equally well-encapsulated try {} catch{} code. E.g. compare:
Only for poor quality code!
if (!handleError(apiCall(args..)))
return;
or
if (!apiCall(args..) && !handleError(errno))
return;
to
try {
apiCall(args..);
} catch(XxxException &e) {
if (!handleError(e))
throw;
}
Arggh! Typical problem with error return code apologists. You are
doing it wrong. You are unable to change your mindset of handling
error for each function call one by one. Write the normal path,
handle errors where appropriate. The above is an perfect example of
poor quality code.
Exception code should (at worse) look like:
void foo()
{
try
{
// Normal path
// several lines of code
// implement all the normal processing logic here sequentially.
}
catch( /* something */ )
{
// handle errors
}
}
In many cases, it should look like:
type2 foo1()
{
bar1();
bar2();
bar3();
type t = bar4();
type2 z = bar5(t);
return z;
}
bar1()
{
buzz1();
buzz2();
// ...
}
boss()
{
try
{
type2 t = foo1();
foo2(t);
// ...
}
catch( /* stuff */)
{
// handle errors
}
}
The amount of error handling code can easily become 1/4th to 1/10th of
the amount used by return status code style programming.
/* if apiCall does not encapsulate exceptions from *its library's* underlying
libraries, the above code would be even longer */
- it is fater because compiler can optimize nothrow code. In a (probably futile)
attempt to prevent pointless objections to this statement I am referring you to
the Ultimate Authority of the Standard, see a footnote to 17.4.4.8-2 in ISO/IEC
14882:2003(E) or 17.6.5.12-3 in ISO/IEC 14882:2011(E) about the reason why C
library functions shall not throw:
Why are you talking about C library functions? Of course C library
functions shall not throw. C doesn't have exceptions. We are
discussing C++ here!
It's slower because you have 100 if/else statements in the normal path
where I only have 1 try/catch
It is slower because in the normal success processing path, you
repetitively have to check for error while with exceptio
e.g. compare:
// Critical loop:
for(int i = 0 ; i < BIG_NUM ; ++i )
{
int retVal = foo();
if(SUCCESS != retVal)
{
// handle error
// break/return/exit?
}
}
vs
try
{
for(int i = 0; i < BIG_NUM; ++i)
{
foo();
}
}
catch(...)
{
// handle error
}
The normal path has at least 1 more assignment and 1 more test. Things
become even worse if the function actually return a value (see below)
It's slower because you have to define a object with dummy value, pass
it as argument to a function, copy data into it then finally use it
while I initialise the object directly with valid value and the
compiler can optimize the code with NRVO/RVO
It is slower because the compiler can't optimize code as nothrow
simply because there are no explicit throw/try/catch statements in
your code. If anywhere down the line something uses the C++ standard
library, new or an allocator, then exceptions may occur.
".. This allows implementations to make performance optimizations based on the
absence of exceptions at runtime."
In C++, that's playing ostrich. (as in bury your head in the sand and
claim it's not there because you can't see it) You should always
assume the any function call can throw.
At best, it can be equal, and in practice, it must necessarily be more
because you're explicitly doing things that are done implicitly with
exception handling.
Whom do you mean when you say "you're"? Me a library writer or me a library
user? Me a library writer is supposed to go extra mile to better serve a library
user. Me a library user will have to do more and more complex things when I use
exception-based API which my this and previous and previous-before-previous
posts explain at monotonously increasing and unnecessary for a reasonable reader
level of details.
Strongly disagree. As a library user it is much simpler to use an API
that is exception based than an API that is return code based.
As a free bonus, you (or your user if you are the library writer) can always
make a choice between writing throw and nothrow functions at no coding cost.
Hardly. Just returning error codes doesn't suddenly let you mark
everything nothrow,
It allows you to "mark everything nothrow" without writing try-catch blocks.
Marking functions nothrow was not counted as part of the cost because it had to
be done regardless of the type of the underlying API if the decision to write
nothrow functions has been made.
There you are again playing ostrich. As discsussed by others in this
thread, if you want nothrow, you must never use new or the standard
C++ library anywhere and you must enable the "no exception" flag in
your compiler. Anything else is unsafe.
due to the problem I mentioned above.
On top of this, following the code that only calls nothrow methods is much
easier which increases the programmer's productivity.
I fail to see how calling a method marked nothrow increases my
productivity at all.
As the rest of the paragraph explained, in increases your productivity by
reducing the time you need to read and understand the code with only explicit
exit paths. Oh, I forgot, you are not spending much time reading code.. this "on
top of it" advantage is probably not for you then.
Strongly disagree. In my experice, reading exception based code is
much simpler since I can read the normal success path and concentrate
on the sanity of the success scenario independently of the error path.
I can then read the error path and concentrate on the sanity of the
error path. And since the code is typically significantly smaller, I
have to spend less time reading.
Typical error return based code in C++ is a nightmare to review. It
is typically exception unsafe because it blindly assume that because a
specific function implementation does not has explicit throw
statements, then exceptions can be ignored. As a result the code will
often be totally exception unsafe and if anything below generate an
exception, then everythign breaks.
Even ignoring this simple fact, the repetitive "doA, check if A
succeeded, handle A-type error, decide if processing can continue, if
not release previosuly acquired resources, doB, check if B succeeded,
handle B-type error, decide if processing can continue, if no release
previously acquired resources including those acquired for doA, ... "
is very verbose and very error prone.
It doesn't reduce what I need to be worried or
concerned about unless the code actually provides the nothrow safety
guarantee, which is considerably difficult.
Really? Valid C++ code that does not throw any exceptions itself and calls only
functions with an empty exception specification is guaranteed not to throw an
exception. Incidentally not throwing an exception *is* the definition of nothrow
exception safety guarantee.
please review what "nothrow" actually does and what are the
consequences if a function declared as nothrow does let an exception
escape.
Merely not throwing exceptions is of very little benefit to the
programmer.
I am not sure about that vague "merely not throwing exceptions" but exposing
only no-throw functions from a library API (which was my original and only
point) brings all the benefits I claimed.
It brings some benefits for some style of programming. It however
bring cost for some other style of programming. I prefer an library
that uses exceptions. Much easier to use.
Let's take the Eigen library for an example:
MatrixXd m(2,2);
m(0,0) = 3;
m(1,0) = 2.5;
m(0,1) = -1;
m(1,1) = m(1,0) + m(0,1);
Very usable. Now let's try to rewrite this with error code:
MatrixXd * pm = MatrixXdFactory(2.2);
if(NULL != pm)
{
int retVal = 0;
retVal = pm->Set(0,0,3.0);
if(0 == retVal)
{
retVal = pm->Set(1,0,2.5);
if(0 == retVal)
{
retVal = pm->Set(0,1, -1.0);
if( 0 == retVal)
{
float v1 = 0;
retVal = pm->Get(1, 0 , &v1);
if( 0 == retVal)
{
float v2 = 0;
retVal = pm->Get(0,1, &v2);
if(0 == retVal)
{
float v3 = v1 + v2;
retVal = pm->Set(1, 1, v3);
if(0 == retVal )
{
// Success!!!!
}
}
}
}
}
}
}
}
}
// handle error?
return retVal;
Yuck!
Feel free to rewrite the above with error output parameters to
demonstrate how much better again the code would be and how much
easier to read and review.
Yannick