none said:
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.
Not for the error generated by the code under your control, no. You may have
good-for-your-purpose conventions on error models and error processing. You can
catch and process *your* errors wherever appropriate -- as you rightly describe
below.
But more likely the not an arbitrary selected library you are using does not
obey your error conventions. This is not a library writer's fault. Objectively,
a library cannot follow many useful conventions (e.g. error severity is often
specific to client code's context and cannot be determined in the library).
Therefore, you often need to *convert and enrich* error conditions signaled by
several used libraries, each according to its own conventions, to your
conventions. *These conversions* is what is usually best encapsulated in the
immediate callers (to minimize the amount of code exposed to foreign error
conventions which in turn minimizes the scope of the changes required when you
switch to another library or a new version of same library or add to or replace
your code surrounding the library calls).
Because I have been changing library writer and library user hats often and, as
library user, I found non-throwing libraries much more pliant to conversions of
their errors to the conventions required by my code, I follow the golden rule
when I am designing libraries: do to other library users what I want library
writers do to me when I am a library user -- that's it.
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.
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.
The above is fine -- as long as the exception type hierarchy is under your
control (that is, higher than the conversions at the call stack).
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 reasonable if you cannot possibly catch this error where you have useful
information about the error's meaning *for you* (and off the bat I cannot come
up with a case where it would be *technically* impossible).
If, which is often the case, it was your choice to catch that error in a place
where you cannot read its meaning-for-you -- I would not call it reasonable. I
would rather call it a design error.
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.
I am not sure I understand this statement. In my opinion, exceptions are a C++
feature, yet another tool made available to practitioners, not a behavior.
If what you mean is that the default operator new does not return 0, it is a
reasonable (although maybe not the best) default choice *for this particular
error, which is unique in that most applications will want to terminate when
they run out of memory* (I am saying "maybe not the best" because if I do want
to terminate, I would prefer to abort() (which dumps core, on many systems) as
close as possible to the call site of the failed memory allocation call to
investigate the problem easier).
On the other hand, the other errors, such as i/o or formatting errors (to which
category strtol() mentioned by me falls) are *not* signaled via exceptions by
default in C++ -- for example, you have to call exceptions() on a stream to make
it throw -- which is, I think, also reasonable. It is unfortunate, however,
that, at least in practice, you have to pay for the cost of calling potentially
exception-throwing functions even when you do not turn any stream exception on.
This is totally backward:
Are you seriously grouping exceptions with errno? This is hard to believe.
In terms of giving a programmer a syntactic hint about error condition an API
can raise? Of course I do. Namely, neither of the above gives any syntactic clue
to an API user whether a function call can raise an error condition and, if yes,
how to catch it.
This is so back to front. Exceptions absolutely can't be ignored
passively.
You are probably talking about run-time. I am talking about coding time. At
coding time, exceptions perfectly can be ignored (and in fact quite often "the
can" is being "kicked down the road"). When this happens in a library (I mean,
ignoring the exceptions thrown by a library underlying this library), the onus
is put to the library user -- often without any notice and for their own money.
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.
please see above
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.
Same with the unspecified exceptions -- nothing *in the code* indicates such
deliberate choice (this is somewhat specific to C++; Java is notoriously different).
With expection, the error will *always* interrupt normal processing
*unless* the programmer explicitely write code to resume normal
processing. This is a *good* thing.
Now you are talking about run time. Also your model of "a programmer" is
limited; in a real-life application it is often that there is more than one
programmer in the picture: programmer lp1 wrote library l1, programmer lp2 wrote
library l2 that calls l1 and neglected to catch any exceptions; then programmer
ap wrote application a that calls l2 and has no clue about l2's use of l1 (it's
not ap's fault because we already know lp2 is sloppy; he did not necessary even
documented it for ap that l2 uses l1).
ap diligently catches documented exceptions of l1 at all correct points; but
once in a while his program has inexplicable problems (whose root cause are
exceptions from l1 but he won't before much later). ap just picked up the tab..
If this situation is not familiar to you, you are either extremely lucky or do
not maintain big software systems a lot.
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.
See above. This happens all the time and this is not even always the last
non-catching programmer's fault.
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.
You are missing the point. Whether or not you have to convert every error of 3rd
party library into your error is often not a choice but a requirement.
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.
Why are you talking about C library functions?
I am not talking about C library functions. I am talking about the fact that the
authors of the (C++) Standard recognized and explicitly acknowledged that
implementations can optimize functions explicitly declared not throwing. (From
half-empty glass perspective, by that they admitted the significance of
pessimization caused by throwing functions).
Of course C library
functions shall not throw.
It would be instructive for you to read the footnotes I referred to to learn
*why exactly they shall not throw* (from the viewpoint of C++ Standard authors,
not mine).
C doesn't have exceptions.
Even if this is a rationale of C++ Standard authors for requiring C functions to
be no-throw, it is not written down in the Standard. The performance rationale
is written down.
We are
And I am fully aware of that. And you are fully aware that I am aware.
It's slower because you have 100 if/else statements in the normal path
where I only have 1 try/catch
It does not make sense to compare the amount of ifs in the pieces of code that
do different things. My two code snippets are comparable to each other in their
functionality (a uniform and informed handling of all error conditions of a
3rd-part library. I defined this purpose for your multiple times). Your code
snippets are doing something else. It would be useful if you defined the purpose
of your code before writing it.
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)
The Extra assignment is only in your code because error processing is not
encapsulated (see my code snippet above that does not have that assignment). Of
course, even the way you wrote it, the assignment does not have to appear in the
machine code at all.
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
With return codes, you do not have to pass extra parameters. If you allude to
not being able to return value along with parameters, you can always group the
return code into std:
air. STL containers sometimes do just that. RVO works
just fine on a pair; often the return value and return code never hit main
memory -- as long as there enough registers. Of course, return code does occupy
a register -- but exception and overhead of a throwing function's
prologue/epilogue cost much more than one register.
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.
Yes, it can and it does. Please refer to the Standard and study the assembler
code I posted earlier in the thread "Using printf in C++" or generate and study
the code of your own.
If anywhere down the line something uses the C++ standard
library, new or an allocator, then exceptions may occur.
That they can. What they can't is to be thrown from the function declared as
no-throw. std::terminate() will be called first (and std::terminate() does not
return -- or throw).
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.
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.
Not if you need to process errors according to strict policies about which the
library author did not and could not know when s/he wrote it.
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.
A compiler with enabled "no exception" flag is not a standard C++ compiler (at
least it was not according to the old standard; I did not check the new one at
this).
As for never using "new" under a no-throw function this is another fallacy,
easily disprovable in practice at that. Please try this simple test on your
favorite most compliant C++ compiler (with the exceptions turned on):
#include <iostream>
#include <vector>
using namespace std;
void
memoryBuster() throw()
{
vector<double> v(10);
for (;
{
v.resize(v.size() * 2);
}
}
int
main(int, char*[])
{
try {
memoryBuster();
} catch(...) {
cout << "memory buster has thrown" << endl;
}
return 0;
}
With a compliant compiler, you will get:
a. with uncommented throw() specification: terminate() called and no "memory
buster has thrown" output.
and
b. with commented-out "throw()" specification: "memory buster has thrown" output.
Anything else is unsafe.
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 where are you going to search for the error path? Throughout the whole code
base?
And since the code is typically significantly smaller,
The code that is functionally equivalent will be of comparable size (depending
on the required functionality, slightly smaller or slightly bigger. In my
experience, when you need to adhere to a strict non-trivial error-processing
policy, it is slightly bigger).
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.
How is this relevant to my post? My post recommends to design a library API from
no-throw functions. And, if a function is declared no-throw, then, *yes,
exceptions can be ignored*. In fact, you do not have any other useful choice
when you are calling a no-throw function *because you are never going to catch
an exception from it, neither at call site, nor in main(), nor anywhere in
between*.
As a result the code will
often be totally exception unsafe and if anything below generate an
exception, then everythign breaks.
irrelevant to my posts.
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.
irrelevant to my posts.
please review what "nothrow" actually does and what are the
consequences if a function declared as nothrow does let an exception
escape.
Please follow your own advice and review what non-throwing functions (those
declared as throw() or equivalently) do and memorize once and for all that they
do *not* "let exceptions escape".
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);
Why don't you define the required error-processing functionality of your
application before writing any code for a change?
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!!!!
}
}
}
}
}
}
}
}
}
It is completely pointless to evaluate the code above whose only purpose IMHO is
to demonstrate strangely looking code. Neither snippet tells me anything about
how either retVal or (invisible and unhandled, contrary to all your "impossible
to ignore" statements) exception is intended to be dealt with.
Specify the possible error conditions and desired error processing policy of
your library, specify how the exception version of the API signals these
conditions and demonstrate how you implement your specified policy over it and I
will show you how to implement same policy with an alternative
(non-exception-based) error-signaling API in a competitively clear and concise
code.
Please notice that I have already specified the case (which is often in
enterprise applications practice) where the code based on exceptions is
necessary bulkier.
// handle error?
return retVal;
Yuck! Bon appetite
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.
I will gladly do so when I understand your desired error handling policy and see
your code that is compliant with it.
-Pavel