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!
>>
>> That's what I always profess. With exceptions, however, it might be unclear
>> where's Rome and where's Athens. Just a quick example off the top of my head:
>>
>> When using STL, use std::exception hierarchy.
>> When using WFC, use System::Exception.
>> When using Qt, use return codes, but for passing errors between threads, inherit
>> your exceptions from QtConcurrent::Exception
>>
>
> You're going to have to clarify what you mean by WFC. I don't think
> it means what you think it means. If you mean the .NET runtime, then
> you can't use that in C++, so it's somewhat irrelevant. C++/CLI is
> not C++.
Interesting statement. To be considered seriously, however, it requires much
better support than a simple assertion.
>
>> When you write your own library that will most often used with WFC, often with
>> Qt and always with some STL -- how shall you expose your errors?
>>
>
> With an exception, inheriting from an STL one if you must inherit from
> one (since it is 'always' used), though your problem honestly makes
> little sense.
I do not think this statement is relevant to my point. The facts of life are:
a. C++ allows using exceptions not inherited from std::exception.
b. Many significant libraries use such exceptions.
c. Not using such libraries while writing your library or app is often not an
option.
d. Even when not using such libraries is an option in a particular case, it is
almost never wise to assume that this library or app *will never be used* with
such a library.
>
>> When you write your own library that uses WFC -- and of course STL how shall you
>> expose your library's errors and where should you catch std::exception (which
>> are fortunately rare) and guys from System::Exception hierarchy (which are much
>> more often)?
>
> In general, if you have to catch std::exception or System::Exception
> outside of the entry point of your program[1][2],
First of all, this does not make sense: the whole point of exceptions is that
you can catch them in more than one place.
Regardless, it is irrelevant: even if the above were true, it would still result
in duplicating code to implement app- or library- specific error handling policy
for STL's std::exception and other library's exceptions not derived from
std::exception.
you've done
> something pretty wrong. I think you're trying to create artificial
> problems.
Ad hominem? So far I have refrained from conveying my impression from this
discussion: that you are either in denial or have had little experience with
maintaining a useful library or application.
>
>> When you write an application that uses all 3 or even only two libraries -- what
>> errors shall you use inside your application? Many large application require
>> particular processing of particular error conditions (error logs, alarms,
>> interfaces to enterprise event handling systems, what's not) -- are you going to
>> proliferate these exceptions? Usually, you don't.
>
> 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()).
You seem to be confusing the response to
> a condition with the condition itself,
As you are not providing any support for your statement, we should just believe
it, correct?
> but I don't see anything here
> that requires "special" handling or creates a problem.
It seems to me you cannot "see anything here" because you are carefully avoiding
actually reading my posts.
> Nevermind that
> I'm not quite sure how you'd mix the 3 libraries you mentioned in the
> first place.
Any two of them would be enough, as my post clearly said -- but you chose not to
read it before answering, correct?
>
>> The benefits of uniform error
>> processing aside, why will you want to make more parts of your code dependent on
>> the libraries only because their exceptions may penetrate them and the errors
>> are need to be processed there? What if you need to replace such a big library
>> with another monster (e.g. to port your library). How are you going to tear
>> these dependencies out?
>
> Huh? The only way I can observe an exception from a given library is
> if I'm actually calling functions within the library. As such,
> needing to handle the exception can't possibly make me more dependent
> on the library!
Yes, it can. Do you really believe that dependency on a library is a boolean
yes/no variable?
Then just wait till your business manager asks you how much time it will take to
migrate an app from library XYZ to library ABC and answer him "No" (or "Yes" if
you are a yes-man; the outcome will probably be same if he or she is an
effective manager).
You're going to need to provide an actual example of
> what you're talking about.
I already all but provided you the example of the code you will have to write to
encapsulate the dependency on exceptions and on error codes. As you chose not to
understand that, I do not have any reason to believe that providing even more
details will serve to any purpose but your attempts on nit-picking.
>
> If you're writing exception handlers to catch exceptions from
> libraries you do not call (directly or indirectly),
You really did not read my post before answering it, did you? Otherwise, how
could you possibly mistake "the library calls your application uses" for
"libraries that you do not call"?
then you're doing
> something completely and totally wrong.
then.. of course. It is so convenient to blame others for things they did not
do, isn't it?
>
>> If you are a responsible designer, you will provide your own error processing
>> layer and you will have to meticulously catch and either process or crack and
>> refactor all errors before letting them further in a thin layer wrapping the
>> library calls your application uses.
>
> No, the code that responds to an error condition really shouldn't care
> how you received the condition initially:
Sure it should even if because it syntactically has to. If you received the
condition via std::exception, you have to write "catch(...[std::]exception..)"
and if it was via AbcException that is not derived from std::exception, you have
to write catch(...AbcException..).
> if you're going to log a
> message in response to an error, the logging code doesn't care if you
> got an exception, bungled about with return codes, or just made things
> up. The same goes for displaying dialogs and just about every other
> conceivable response.
Vague "logging code" and "any other conceivable response" may not care (you like
vagueness, don't you?), but the more specific code "calling logging function" or
"filling up dialogue fields" has to care, because otherwise:
a. It won't be called at the first place, because a specific exception type
won't be caught.
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.
> The entire point of exception handling is that if you cannot (or need
> not) respond to the condition, /you pretend that it does not exist/
> [3].
I would not put it this way but, yes, the whole point of exception handling is
to facilitate handling error conditions at desired points in code. I am glad we
agree on this. I am sure you remember that I specifically stated that the
exceptions should not be thrown across library boundaries, not in general.
"Kicking can down the road" to your own code is ok. Kicking it across library
boundary is IMHO as irresponsible and morally objectionable as the
loved-by-USA-politicians kicking national debt to the next generation. Why are
you sure your library user will enjoy letting your stray std::exception to
terminate his or her program or catching it somewhere at his main() where he
only expects exceptions from his own functions or none at all?
> If you're doing something else, then you're designing your code
> entirely incorrectly. Such behavior is the height of
> irresponsibility, because it is a fool's errand.
You love denouncing actions you just made up, don't you? In conjunction with
your aptitude for kicking the can down the road, this makes me wonder: are you a
politician by any chance?
C++ libraries need
> not declare every exception they throw, so without walking every line
> of code of every library you use (directly or indirectly), one cannot
> even accomplish what you say should be done.
Exactly. Now you are at last talking about the subject of my post. If one of
your requirements is to log every error in a particular manner, it is
significantly more labor-consuming to do that "walking ever line of code" (which
you will have to do, no matter how much you hate the idea) with the
exception-based API than with error code-based API. First, the error code
dispatching is easier to formalize and then drive from configuration. Second,
error code-based error processing methods usually include some error types in
API function prototypes (as opposed to exceptions) so you will always have a hint.
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()).
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:
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.
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.
>
>> 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:
if (!handleError(apiCall(args..)))
return;
or
if (!apiCall(args..) && !handleError(errno))
return;
to
try {
apiCall(args..);
} catch(XxxException &e) {
if (!handleError(e))
throw;
}
/* 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:
".. This allows implementations to make performance optimizations based on the
absence of exceptions at runtime."
> 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.
>
>>
>> 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.
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.
> 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.
>
> 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.
>
>> successful real-life applications and libraries (which is equivalent to saying
>> "applications and libraries of significant size") have to spend more time on
>> reading and understanding code of others than on writing their own code.
>
> Funny, I spend more time trying to figure out what was written in all
> these technical papers I'm supposed to implement than looking at
> anyone's source code. YMMV I guess.
>
>>
>> Do your math yourself. I did mine many years ago and have never had a reason to
>> be sorry of this my application of golden rule.
>
> Unfortunately, the equation is changed, but based on what you've said
> I think you did the calculation completely incorrectly in the first
> place. Actually supporting your position with real code,which I
> strongly encourage you to do, is going to be rather difficult.
I provided more than enough use cases (and a little code snippet above, in this
post) to support my position whereas you chose to provide no use cases, let
alone the code, to support yours.
>
> Adam
>
> [1] Or the entry point to a thread or similiar concurrency construct
> [2] Though one should almost certainly 'catch(...)' in these contexts.
> [3] Handling it here includes cleaning up resources and then kicking
> the can down the road.
> [4] The occasional syntax foible aside.
-Pavel