Exceptions: Bad error handling? (Was: What garbage collection...)

Started by
22 comments, last by MaulingMonkey 18 years, 11 months ago
Forking off the discussion: here
Quote:Original post by Jan Wassenberg Frankly I have little interest debating the use of exceptions, especially when you only partially read what is presented and then scoff at it without understanding. But hey, let's give it a shot.
Quote:Switching this to using RAII and exceptions would look something like this:
Since you clearly haven't read the comments, let this reply address what you proposed: In your C++ version you're conveniently glossing over the complexity of, for example, making sure the file map is destroyed after the view (because the view relies on the file map). Are you going to use reference counting? [edit: code tag removed]
How about just having the mapping class destroy it in it's destructor? Like you would do if you were using RAII, which is what we've said we're doing? Usually, file mapping does use reference counting or OS mechanism to prevent the same file from being repeatedly mapped to multiple locations within one process space, but there wouldn't appear to be a need for that. If you need to have the mapping stick around longer, you keep around that object longer. The lifetime of the mapping is tied to the lifetime of the mapping class.
Quote:Actually I'm glad you brought this up, because it illustrates perfectly the point being made: it is much easier to fall flat on your ass with exception cleverness, as opposed to error codes (which may be bulky but get the job done and *are in plain sight*).
I disagree. The two versions would preform exactly the same. Let's assume for a moment that it's illegal behavior to release the file mapping hfm before unmapping the pointer pv, in this snippit (I program mostly on/in parallel with linux, so I don't know if this is true):
  HANDLE hfm = CreateFileMapping(h, NULL, PAGE_READ, 0, 0, NULL);
  void *pv = MapViewOfFile(hfm, FILE_MAP_READ, 0, 0, 0);
Using the RAII version using stack variables, the objects will be destroyed in the opposite order of creation, which means it's pretty much (is?) impossible to accidentally screw up the order (although one could intentionally saboutauge the effort by the manual calling of a destructor...):
file hfm = CreateFileMapping( h , .... );
mapping pv = MapViewOfFile( hfm , .... );
pv will allways be destroyed before hfm in this case. Assuming we wanted to hold onto the mapping for an indefinte amount of time, let's say we were passing it around via boost::shared_ptr:
boost::shared_ptr< mapping > pv = new mapping( ... );
Making sure that it's dependancy never dies would be a simple matter of either ownership or another shared handle, as members of the mapping class - or as a wrapper around both, which is easy to implement.
Quote:
Quote:(ignoring the fact that I can't return an error code and an icon, another problem of error-code returns):
Easy solution: either multiplex them (e.g. if 0 is returned, call GetLastError) or return data in an OUT parameter.
The only fully general solution is to allways have a GetLastError or equivilant. Case in point: atoi, which returns an integer, for which all values are valid, yet some inputs are invalid ("hello world" does not convert to integer). Assuming an error is ignored by a programmer, the program will continue on with perfectly valid results from an invalid setup.
Quote:
Quote:I laughed when I read this: "It's really hard to write good exception-based code since you have to check every single line of code (indeed, every sub-expression) and think about what exceptions it might raise and how your code will react to it. (In C++ it's not quite so bad because C++ exceptions are raised only at specific points during execution. In C#, exceptions can be raised at any time.)" The entire point of exceptions is that you don't have to check every single line of code's return, and deal with it by hand, as you would without.
If you don't know of Raymond Chen, that says more about you than him.
I have a hard time remembering (and correctly attributing) names. It runs in the family. My uncle once called me by his own name, by accident, influenced by how often my mother and grandmother called me by his name. I can't even remember the Govenator's real name right now.
Quote:If you do and are still laughing, then you probably haven't understood what's been said. So let's have another look: with return codes, you have to make sure each function call is wrapped in a CHECK_ERR macro (discussed by Alexandrescu under another name) or similar. Compare this with exceptions, where analysis is practically impossible because even trivial code sequences have an astounding number of paths through them. In particular, construction of [temp] variables may fail, throw, and leave your calculation in limbo.
More likely, the calculation will be discarded entirely. If you're refering to a longer state object such as an exception thrown during the insertion of a new object into, say, a linked list, programming in consideration of Exception Guarantees, one could make, for example, the strong guarantee that the list will remain unchanged, leaving your state right as it was before you started. One can also make the guarantee not to throw. It is the fault of your library if 2 + 2 can thrown an exception, not the fault of exceptions.
Quote:So instead of just looking for function calls and seeing that there's error handling code (direct or via CHECK_ERR), you have to think through every part of your code.
And how does directly doing it by hand, or via macro, save you from this task? For every point an exception could be thrown, there should be a corresponding error code to be checked.
Quote:Clearly RAII alone isn't enough to save you, as shown above.
Shown how?
Quote:And when you revisit code, who knows if anyone even gave a thought to error handling? After all, in exception-based code, that need not be apparent.
That's what documentation is for. //This code makes a Strong Exception guarantee. //This code makes a Basic Exception guarantee. //TODO: Check that code makes a Nothrow guarantee (e.g. add throw() to the //function and recurse into the subfunctions until all potential throwing //points have been verified to not throw. The same considerations must be accounted for in an error based mechanism all the same. if ( file_log.add_entry( "..." ) != OK ) { //What is the state of file_log? Can I try again after I've deleted some //porn, or has half of my message been written, the other half lost, and //my file descriptor invalidated? All of this depends on the guarantees //add_entry makes - exception based or otherwise. }
Quote:Now where does that leave us? Exceptions are surely good in some contexts (e.g. checking if object construction went OK), but this is much more limited than their actual use (c.f. the disgrace that is Xerces' end_of_entity "exception").
I won't disagree that exceptions are easy to abuse. Then again, just about everything is - operators for defining language grammar in C++ thanks to boost::spirit, PHP's DLL interfacing functions to create OpenGL programs, the list goes on.
Quote:I like the exception use guide presented in one of the last mails in the current thread:
Is that a list of ANDs, or a list of ORs, out of curiosity?
Quote:
Quote:It's very easy to write good exception-based code once you know how to.
Looks like that's only true if using the Ostrich algorithm: stick_head_in_sand(). Unfortunately it really isn't that simple, or there wouldn't be entire books on the topic ;p
There are entire picturebooks on the ABCs, that dosn't mean they're not simple. Less silly would be pre-algebra books. If duly motivated, I could probably write an entire book on rootbeer. Just because there's entire books on a subject, does not mean that it's not necessarily simple. Besides, you've missed that I've taken the assumption that we allready KNOW how to do it (see newly added emphisis).
Advertisement
I find the original anti-exception argument bizarre, to be honest. I can certainly see that exceptions mean that you have a large number of factors to consider. But when it comes down to it, If you have 10 lines of code and they can throw X exceptions, that's X error conditions that you have to bear in mind regardless of the programming model in use. Converting that code to use return values for errors doesn't decrease the number of error conditions you have to bear in mind - it just makes it easier to inadvertently forget to handle one.
Thanks for the fork :)

Quote:How about just having the mapping class destroy it in it's destructor?

Of course. You still haven't read all comments there; the simple RAII solution has been found lacking.

Quote:The only fully general solution is to allways have a GetLastError or equivilant. Case in point: atoi, which returns an integer, for which all values are valid, yet some inputs are invalid ("hello world" does not convert to integer).

Agreed, atoi has a bad interface since it doesn't provide for indicating errors.

Quote:have a hard time remembering (and correctly attributing) names.
Alrighty. Just pointing out that this blog is not a "hoax" ;)

Quote:One can also make the guarantee not to throw. It is the fault of your library if 2 + 2 can thrown an exception, not the fault of exceptions.

This is actually one thing that sucks about C++: due to implicit conversions, that may very well be UDTs that are allocating memory/performing complex calculations, which may end up throwing. So my point is, you cannot see in this code that it's safe. Even if the requisite ops give a guarantee, you first have to navigate there and see. Now in C, 2+2 just won't blow up in your face.
yesyes, "explicit" and all, but exceptions are still a prime case of something that may blow up without being visible in the source.

Quote:And how does directly doing it by hand, or via macro, save you from this task? For every point an exception could be thrown, there should be a corresponding error code to be checked.

Right, but those failure points are limited to function calls, as opposed to anywhere (including 2+2) when using exceptions.

Quote://This code makes a Strong Exception guarantee.
//TODO: Check that code makes a Nothrow guarantee (e.g. add throw() to the
//function and recurse into the subfunctions until all potential throwing
//points have been verified to not throw.

That's more like it. Rather than a utopic five-liner, we now have documentation to add, RAII classes to write, and painstaking verification to do. If you end up thinking that's still better than writing straightforward macro definition+usage code, fine, but let's not misrepresent the effort involved.
This is exactly what I am trying to dispel. Advocates of exceptions immediately think of long if(err == ENOMEN); else if(err == ENOENT) chains when considering "manual" error handling, but really for equivalent functionality you need even uglier catch(BadAlloc) {} catch(whatever). Rather than what you call "really easy to write" exception code (RAII, ok, but exchange-with-temp is ugly to me), really all that is needed is a simple macro around each function call.
And thereby everyone sees: this code has been checked; it handles errors; it cannot blow up in unexpected ways.

Quote:Is that a list of ANDs, or a list of ORs, out of curiosity?

I'd see it as ANDs, a list of everything that applies.

Quote:There are entire picturebooks on the ABCs, that dosn't mean they're not simple. Less silly would be pre-algebra books. If duly motivated, I could probably write an entire book on rootbeer. Just because there's entire books on a subject, does not mean that it's not necessarily simple. Besides, you've missed that I've taken the assumption that we allready KNOW how to do it (see newly added emphisis).

Oh come on. There is a difference between picturebooks/mass-produced "literature" and technical books that are widely recognized as useful.
And the assumption that you know exception safety cold is quite a large one; imagine the vast majority of coders and tell me you'd trust them to do that ;)
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
Quote:Original post by Jan Wassenberg
Thanks for the fork :)

Anytime, didn't want to completely offtopic that subject.
Quote:
Quote:How about just having the mapping class destroy it in it's destructor?

Of course. You still haven't read all comments there; the simple RAII solution has been found lacking.

I read a lot of them, and used keyword search for RAII, and didn't find anyplace in the comments section where it was found lacking that appeared to have merit. The only comment in the blog itself was "(Yes, there are programming models like RAII and transactions, but rarely do you see sample code that uses either.)"
Quote:
Quote:have a hard time remembering (and correctly attributing) names.
Alrighty. Just pointing out that this blog is not a "hoax" ;)

Ahh, very well, carry on :-).
Quote:
Quote:One can also make the guarantee not to throw. It is the fault of your library if 2 + 2 can thrown an exception, not the fault of exceptions.

This is actually one thing that sucks about C++: due to implicit conversions, that may very well be UDTs that are allocating memory/performing complex calculations, which may end up throwing. So my point is, you cannot see in this code that it's safe. Even if the requisite ops give a guarantee, you first have to navigate there and see. Now in C, 2+2 just won't blow up in your face.
yesyes, "explicit" and all, but exceptions are still a prime case of something that may blow up without being visible in the source.

But in C, you'd simply be calling a function to get the same effect, which would blow up all the same.
Quote:
Quote:And how does directly doing it by hand, or via macro, save you from this task? For every point an exception could be thrown, there should be a corresponding error code to be checked.

Right, but those failure points are limited to function calls, as opposed to anywhere (including 2+2) when using exceptions.

Behind the hood, if 2+2 is throwing an exception, it's because it's a function, and it's getting called. The only difference is the method the error is transmitted (to catching point rather than by return code) and the semantic food coloring.
Quote:
Quote://This code makes a Strong Exception guarantee.
//TODO: Check that code makes a Nothrow guarantee (e.g. add throw() to the
//function and recurse into the subfunctions until all potential throwing
//points have been verified to not throw.

That's more like it. Rather than a utopic five-liner, we now have documentation to add, RAII classes to write, and painstaking verification to do. If you end up thinking that's still better than writing straightforward macro definition+usage code, fine, but let's not misrepresent the effort involved.

It's still a utopic five-liner, in usage :-). Once the RAII class is written, you're done, and it's reusable. Even with a comment stating how well it's been checked for sanity, which the macro version listed in the blog fails to do (it hasn't had all the error codes checked, it hasn't stated wheither or not it makes a strong, basic, or noerror error guarantee, etc). You're adding a set of prerequisites to one version, but not fully to the other. One could go about as a cowboy, without any documentation, and reusing your RAII wrappers.

Let's take a quick creative bending of the API and pretend we allways used a pointer to HANDLE - that's what's returned, that's what's passed as an argument, etc. If this were the case, we'd be able to reuse boost::shared_ptr to do all our dirty work:

//HANDLE * hfm = CreateFileMapping(h, NULL, PAGE_READ, 0, 0, NULL);
boost::shared_ptr< HANDLE > hfm ( CreateFileMapping(h, NULL, PAGE_READ, 0, 0, NULL ) , std::ptr_fun( &CloseHandle ) );

Voila, instant RAII, preforms exactly the same as the blog version, no extra mantinence. Anyone can glance at the code and realize that since we're directly interfacing with the win32 api, and handling our own RAII right there, all the loose ends of our code.

Now, this has taken the creative bending of changing hfm in all it's usages into a HANDLE *. This was done only to make the example work directly with one of the most prominent C++ libraries in an easy to show manner. We could make a template class that would be just as versitile, and allow the actual value to be stored. That is, I give you a functional example:

template < typename value_type >class raii_scope{    value_type value;    boost::function< void ( value_type ) > cleaner;public:    raii_scope( value_type value )        : value( value )    {    }    raii_scope( value_type initial_value , boost::function< void ( value_type ) > cleaner )        : value( initial_value )        , cleaner( cleaner )    {    }    ~raii_scope( void )    {         if ( cleaner ) cleaner( value );    }    value_type & operator*( void )    {        return value;    }    const value_type & operator*( void ) const    {        return value;    }    value_type * operator->( void )    {        return &value;    }    const value_type * operator->( void ) const    {        return &value;    }};


Which acts just like a pointer (so one would be dereferencing) but also holds the value, and preforms an optional cleanup operation upon the value. Here's how it could be used:

BOOL ComputeChecksum(LPCTSTR pszFile, DWORD* pdwResult){    raii_scope< HANDLE > h( CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) , & CloseHandle );    if (*h == INVALID_HANDLE_VALUE) throw exception();    raii_scope< HANDLE > hfm( CreateFileMapping(*h, NULL, PAGE_READ, 0, 0, NULL), & CloseHandle );    if (! *hfm) throw exception();    raii_scope< void * > pv( MapViewOfFile(*hfm, FILE_MAP_READ, 0, 0, 0) , & UnmapViewOfFile );    if (! *pv) throw exception();    DWORD dwHeaderSum;    if ( CheckSumMappedFile( *pv , GetFileSize(h, NULL), &dwHeaderSum, pdwResult)) return true;}


All the detail is right there, which seems to be what you want. This code is safe wheither or not any of those WinAPI functions throw, and things are cleaned up. Personally, I'd wrap it up in seperate wrappers, but this should do what you want.

Quote:This is exactly what I am trying to dispel. Advocates of exceptions immediately think of long if(err == ENOMEN); else if(err == ENOENT) chains when considering "manual" error handling, but really for equivalent functionality you need even uglier catch(BadAlloc) {} catch(whatever).


Only if you're actually planning on dealing with it there, and I strongly disagree that those if statements are any prettier than the catch statements. The nice thing about exceptions is that you can choose to let someone higher up the food chain of the call stack deal with them.

Quote:Rather than what you call "really easy to write" exception code (RAII, ok, but exchange-with-temp is ugly to me), really all that is needed is a simple macro around each function call.
And thereby everyone sees: this code has been checked; it handles errors; it cannot blow up in unexpected ways.


At least, it won't blow up until a single line of code that can throw is inserted into the code - which will probably eventually happen. Then you'll be stuck with the leaks of many handles. As for exchange-with-temp - it's only helpful if you're going for the strong exception guarantee, really, and is much messier if you try and do without. The exact same would apply to error-code based code.

Quote:
Quote:There are entire picturebooks on the ABCs, that dosn't mean they're not simple. Less silly would be pre-algebra books. If duly motivated, I could probably write an entire book on rootbeer. Just because there's entire books on a subject, does not mean that it's not necessarily simple. Besides, you've missed that I've taken the assumption that we allready KNOW how to do it (see newly added emphisis).

Oh come on. There is a difference between picturebooks/mass-produced "literature" and technical books that are widely recognized as useful.

Picture books are a visual learning aid for the young, and are widely recognized as useful in that respect. What I'm getting at is that it can require much effort and material to explain or teach a subject. As children learn their 123s and ABCs, programmers learn their RAII and patterns (visitor, etc). Once learnt, it's like a bicycle, it comes easily and naturally.
Quote:And the assumption that you know exception safety cold is quite a large one;

So's the "assumption" that I'm a 19 year old caucasian male. The only thing is, I'm not making assumptions, I know these to be true :-).
Quote:imagine the vast majority of coders and tell me you'd trust them to do that ;)

To what, PRACTICE exception safety? No, all I have to do is look at the ratio of bad programming books, articles, and tutorials to good, and the number of security patches Microsoft is releasing on any given day to assure myself that half the world can't program worth a ****, and they probably know it. Why should they bother with RAII and fixing memory leaks when there's more important things to focus on - like using more std::string and less of the bounds-checking-less string manipulation functions inherited from C, in an effort to reduce the number of buffer overflows introduced?

When I say we know RAII, I mean I know RAII, and that some of the people here know RAII, and that everyone else should strive to learn RAII. If you don't know RAII, then you're going to be writing leaky code, exceptions or not. Take the time to learn and understand RAII, it will be made worthwhile in the time saved by not having to think of every possible case that your code could prematurely exit and free every so far allocated resource in that scope.

Edit: -&gt; [ source ] + const functions for raii_scope<br><br><!--EDIT--><span class=editedby><!--/EDIT-->[Edited by - MaulingMonkey on May 14, 2005 2:38:10 PM]<!--EDIT--></span><!--/EDIT-->
Quote:I read a lot of them, and used keyword search for RAII, and didn't find anyplace in the comments section where it was found lacking that appeared to have merit."

*sigh* You really could have read the whole thing, rather than skimming over it with the "exceptions are great" mind-filter in place.
With RAII you lose control over destruction order(*) and the possibility of catching errors while freeing the object. In a dtor, there's no way you can pass on e.g. 'handle has already been freed by other buggy code'. Oops.

* although in-reverse-order works in this case.

Quote:But in C, you'd simply be calling a function to get the same effect, which would blow up all the same.

Exactly, but the failure point is marked and much easier to see (no extra "can this blow up?" analysis necessary).

Quote:Behind the hood, if 2+2 is throwing an exception, it's because it's a function, and it's getting called. The only difference is the method the error is transmitted (to catching point rather than by return code) and the semantic food coloring.

Of course. Actually, the explicit syntax of checked functions is what I'm all about here.

Quote:It's still a utopic five-liner, in usage :-).

Well, no. With RAII built in, you add the complexity and ugliness of registering cleanup functions. No more is the utopic code that didn't appear to have any error handling code at all.

Quote:All the detail is right there, which seems to be what you want. This code is safe wheither or not any of those WinAPI functions throw, and things are cleaned up. Personally, I'd wrap it up in seperate wrappers, but this should do what you want.

Looks ok. I have no idea though why you insist on throwing in the failure cases where you could just return an error code. Making your users catch exceptions for normal usage is just contorted and inefficient; better to provide an error code version and piggyback a throw version on top.

Quote:Only if you're actually planning on dealing with it there, and I strongly disagree that those if statements are any prettier than the catch statements. The nice thing about exceptions is that you can choose to let someone higher up the food chain of the call stack deal with them.

heh, of course you're going to have to deal with the error *somewhere*, so the ugly catch will appear. I'm surprised someone finds such syntax and the associated overhead (we're talking kernel entry, stack unwinding, lots of crap going on) prettier, but de gustibus non est disputandum.
As to letting "someone higher" deal with it, I invite you to consider how often specific error codes (beyond a general 'it failed') are actually examined by callers. Exception zealots seem to assume it happens all the time, from really deep call stacks. If that turns out to be the case, CHECK_ERR macros propagate the error with no further intervention necessary (and probably faster, too), so I don't see the advantage there.
Oh, and catch(...) in a mid-level handler prevents important hardware exceptions ("computer on fire") from reaching higher level code and the user. Hope nobody uses those.. oh wait, I see it all the time. *sigh*

Quote:At least, it won't blow up until a single line of code that can throw is inserted into the code - which will probably eventually happen. Then you'll be stuck with the leaks of many handles.

If someone is throwing something that isn't caught, it's a bug, terminates the program immediately, and is therefore switftly corrected. Not a problem.

Quote:As for exchange-with-temp - it's only helpful if you're going for the strong exception guarantee, really, and is much messier if you try and do without. The exact same would apply to error-code based code.

Disagree. Transactional operation is easier when you cannot be interrupted at any time.

Quote:So's the "assumption" that I'm a 19 year old caucasian male. The only thing is, I'm not making assumptions, I know these to be true :-).

Ýou're missing the point here. You can write how you like, but I'm concerned that the vast majority of coders won't write exception-safe code.

Quote:To what, PRACTICE exception safety? No, all I have to do is look at the ratio of bad programming books, articles, and tutorials to good, and the number of security patches Microsoft is releasing on any given day to assure myself that half the world can't program worth a ****, and they probably know it. Why should they bother with RAII and fixing memory leaks when there's more important things to focus on - like using more std::string and less of the bounds-checking-less string manipulation functions inherited from C, in an effort to reduce the number of buffer overflows introduced?

Thank you, my thoughts exactly. Since that's the case, why force a more complicated methodology on them?!

Quote:When I say we know RAII, I mean I know RAII, and that some of the people here know RAII, and that everyone else should strive to learn RAII. If you don't know RAII, then you're going to be writing leaky code, exceptions or not.
Take the time to learn and understand RAII, it will be made worthwhile in the time saved by not having to think of every possible case that your code could prematurely exit and free every so far allocated resource in that scope.

I'm pointing out problems with RAII and you tell me to 'learn and understand it'? hm. BTW, RAII is not The One True Path. People have been writing exception (signal, interrupt, or other form) safe code for decades.
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
Quote:Original post by Jan Wassenberg
Quote:I read a lot of them, and used keyword search for RAII, and didn't find anyplace in the comments section where it was found lacking that appeared to have merit."

*sigh* You really could have read the whole thing, rather than skimming over it with the "exceptions are great" mind-filter in place.


The first article alone, including comments, is 32 pages long on my 1600x1200 monitor with fairly small text sizes. Reading the whole thing would entail reading every little off topic veer the commentators seem to like making, and every repeat of what's allready been said. Could you try pointing out some of the posts that underscore your point prehaps?

Quote:With RAII you lose control over destruction order(*) and the possibility of catching errors while freeing the object.


Lies, lies and deceptions my friend :-) (No, I'm not accusing you of lying, I just like using that phrase - but you are wrong). For a few examples against this, I give you:

void example_using_ifstream( void ){    std::ifstream file1( "file1.txt" );    std::ifstream file2( "file2.txt" );    file1.close(); //manually destroy the file handle} //let file2's file handle be destroyed automatically.void example_using_auto_ptr( void ){    std::auto_ptr< int > int1( new int );    std::auto_ptr< int > int2( new int );    delete int1.release(); //manually destroy int1} //let int2 be destroyed automatically.


Quote:In a dtor, there's no way you can pass on e.g. 'handle has already been freed by other buggy code'. Oops.


There's no way you can directly return an error code. It's valid C++, although considered bad practice by many, to throw during a destructor*.

* this is because if an exception is thrown during stack unwinding caused by another exception being thrown, it will call terminate(), which will end up quitting the program or calling the function set by set_terminate().

There's three paths for dealing with problems, really:

1) Ignore it, it's not serious enough to report.
2) Report it for debugging purpouses, but continue running.
3) Report it, then die a horrible death to prevent unleashing nuclear arms, or otherwise misbehaving.

For destructors, all that code should be doing is unregistering itself, freeing resources, and so forth. If one wants to throw an exception within a dtor, the safest bet is to call std::uncaught_exception() to see if we're allready dealing with a problem, and take an alternate method for reporting the error than throwing it.

We could ignore it, add it to a list of "exceptions to be processed" which could then be rethrown, ala:

std::deque< std::pair< bool , const std::exception * > > unhandled_exceptions;template < typename exception_t >void my_throw( exception_t e ){    if ( std::uncaught_exception() ) unhandled_exceptions.push_back( make_pair( false , new exception_t( e ) ) );    else throw e;}my_throw( easy_to_deal_with_exception() );void handle_exception_in_example( const std::exception & e ){    try    {        throw;    }    catch( easy_to_deal_with_exception )    {        //...deal with it...    }    catch( unable_to_deal_with_exception )    {        throw; //let the higher ups deal with it    }    catch( omg_were_going_to_die_exception )    {        std::terminate();    }    try    {        std::deque< std::pair< bool , const std::exception * > >::iterator i            = std::find_if( unhandled_exceptions.begin() , unhandled_exceptions.end() ,                std::compose1(                    std::bind2nd(                        std::equal_to< bool >() ,                        false )                    } ,                    std::select1st< std::pair< bool , const std::exception * > >()                )            );        if ( i == unhandled_exceptions.end() )        {            std::for_each( unhandled_exceptions.begin() , unhandled_exceptions.end() , deletor );        }        else        {            i->first = true;            throw *(i->second);        }    }    catch( const std::exception & e )    {        handle_exception_in_example( e );    }}void example_function( void ){    try    {        ....    }    catch( std::exception & e )    {        handle_exception_in_example( e );    }}


Quote:* although in-reverse-order works in this case.

Quote:But in C, you'd simply be calling a function to get the same effect, which would blow up all the same.

Exactly, but the failure point is marked and much easier to see (no extra "can this blow up?" analysis necessary).

Quote:Behind the hood, if 2+2 is throwing an exception, it's because it's a function, and it's getting called. The only difference is the method the error is transmitted (to catching point rather than by return code) and the semantic food coloring.

Of course. Actually, the explicit syntax of checked functions is what I'm all about here.


That would be an argument against operator overloading rather than an argument against exceptions. After all, without exceptions, I could still use C++ operator overloading, and set an error code somewhere, so that for "safe" code one must do things like:

int a = b + c;if ( LibraryGetError( LIBRARY_IMPLEMENTOR_FAILED_ELEMENTARY_SCHOOL_MATH ) ){    TauntLibraryProgrammer();}


Quote:
Quote:It's still a utopic five-liner, in usage :-).

Well, no. With RAII built in, you add the complexity and ugliness of registering cleanup functions. No more is the utopic code that didn't appear to have any error handling code at all.


And how is this different from the ugliness of manually cleaning up yourself?

Converting from:

raii_scope< HANDLE > h(    CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) ,    & CloseHandle );


to:

HANDLE h ( CreateFile(pszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) );CloseHandle( h );


I removed:

"raii_scope<" , ">" , ", &"

And I added:

"( h );"

Plus I moved the two, related statements, from being right next to each other, to being on opposite sides of the function.

Quote:
Quote:All the detail is right there, which seems to be what you want. This code is safe wheither or not any of those WinAPI functions throw, and things are cleaned up. Personally, I'd wrap it up in seperate wrappers, but this should do what you want.

Looks ok. I have no idea though why you insist on throwing in the failure cases where you could just return an error code.


For consistancy. It's the choice between consitantly using error codes or consitantly using exceptions.

Quote:Making your users catch exceptions for normal usage is just contorted and inefficient; better to provide an error code version and piggyback a throw version on top.


<sarcasm>Because normal usage of a checksum checking function is using it on a non existant file</sarcasm>

It's an error to try and checksum a file that does not exist. One might argue "I want to checksum a large list, and ignore if the file does not exist". Well, there's a very easy way to write that:

void VerifyChecksum( LPCTSTR pszFile, DWORD someChecksum ){    DWORD computedChecksum;    ComputeChecksum( pszFile , &computedChecksum );    if ( computedChecksum != someChecksum ) throw ChecksumsDontMatch();}void VerifyChecksumIfFileExists( LPCTSTR pszFile, DWORD someChecksum ){    if ( FileExists( pszFile ) )    {        VerifyChecksum( pszFile , someChecksum );    }}


One then calls the second function on all the files, rather than the first. That wasn't so hard now was it? Notice that this won't end up throwing an exception!!! Wonderful isn't it. There's no error handling code here, because we never make the error in the first place.

Quote:
Quote:Only if you're actually planning on dealing with it there, and I strongly disagree that those if statements are any prettier than the catch statements. The nice thing about exceptions is that you can choose to let someone higher up the food chain of the call stack deal with them.

heh, of course you're going to have to deal with the error *somewhere*, so the ugly catch will appear. I'm surprised someone finds such syntax and the associated overhead (we're talking kernel entry, stack unwinding, lots of crap going on) prettier, but de gustibus non est disputandum.


So don't use exceptions to report that the color of green you've just selected in paint clashes with the red that's allready been painted. Use them to report errors - which tend to be rather infrequent. I've allready gone over how I fail to see how this catch statement:

catch( bad_lexical_cast )
{
}

is worse than:

if ( BoostGetLastError() == BAD_LEXICAL_CAST )
{
}

Quote:As to letting "someone higher" deal with it, I invite you to consider how often specific error codes (beyond a general 'it failed') are actually examined by callers. Exception zealots seem to assume it happens all the time, from really deep call stacks. If that turns out to be the case, CHECK_ERR macros propagate the error with no further intervention necessary (and probably faster, too), so I don't see the advantage there.
Oh, and catch(...) in a mid-level handler prevents important hardware exceptions ("computer on fire") from reaching higher level code and the user. Hope nobody uses those.. oh wait, I see it all the time. *sigh*


Right, because WinAmp needs to know wheither or not my computer is on fire. But seriously - I'm talking about higher in terms of "closer to the entry point" rather than higher as in "farther from the entry point". For an OS level item, the entry point would be in assembly. Really low-level stuff, really. "Computer on fire" dosn't get sent to Winamp, it gets sent to the kernel, which then shuts down the system. This is exactly what should happen - let someone higher in the call stack/privilage level deal with it.

Quote:
Quote:At least, it won't blow up until a single line of code that can throw is inserted into the code - which will probably eventually happen. Then you'll be stuck with the leaks of many handles.

If someone is throwing something that isn't caught, it's a bug, terminates the program immediately, and is therefore switftly corrected. Not a problem.


I'm talking about "and is caught, dealth with, and the program continuing as usual".

Quote:
Quote:As for exchange-with-temp - it's only helpful if you're going for the strong exception guarantee, really, and is much messier if you try and do without. The exact same would apply to error-code based code.

Disagree. Transactional operation is easier when you cannot be interrupted at any time.


Not when you have to check every new block of memory you allocate to make sure you actually got it.

Quote:
Quote:So's the "assumption" that I'm a 19 year old caucasian male. The only thing is, I'm not making assumptions, I know these to be true :-).

Ýou're missing the point here. You can write how you like, but I'm concerned that the vast majority of coders won't write exception-safe code.

And I'm concerned that the vast majority of coders won't write error-code-safe code. It's the choice between leaking some resources and not noticing those resources were ever allocated, in the case of an error. One leads to the server eventually crashing unless restarted, the other leads to it immediately crashing due to segfault and/or a possible security hole.

Quote:
Quote:To what, PRACTICE exception safety? No, all I have to do is look at the ratio of bad programming books, articles, and tutorials to good, and the number of security patches Microsoft is releasing on any given day to assure myself that half the world can't program worth a ****, and they probably know it. Why should they bother with RAII and fixing memory leaks when there's more important things to focus on - like using more std::string and less of the bounds-checking-less string manipulation functions inherited from C, in an effort to reduce the number of buffer overflows introduced?

Thank you, my thoughts exactly. Since that's the case, why force a more complicated methodology on them?!


They're beyond hope. I won't try and force exception safety upon them, just like I won't try and force error-checking of any sort upon them. I'm arguing that exceptions are better than return codes, not that one should learn error handling when you have half a bazillion unhandleable bugs in your code.

Quote:
Quote:When I say we know RAII, I mean I know RAII, and that some of the people here know RAII, and that everyone else should strive to learn RAII. If you don't know RAII, then you're going to be writing leaky code, exceptions or not.
Take the time to learn and understand RAII, it will be made worthwhile in the time saved by not having to think of every possible case that your code could prematurely exit and free every so far allocated resource in that scope.

I'm pointing out problems with RAII and you tell me to 'learn and understand it'? hm. BTW, RAII is not The One True Path. People have been writing exception (signal, interrupt, or other form) safe code for decades.


So far, I've seen these arguments on your part against RAII:

1) "Can't control destruction order" - showed example that controled destruction   order.2) "Documentation overhead is higher" - showed you an ugly RAII example.3) "Now it's ugly" - pointed out that it was only 10 characters or so longer,   and was really just as ugly as the posted code.4) "not The One True Path" - subject to opinion ;-).5) "People have been writing ___ safe code for decades" - by using RAII or being   anal-retentive, yes ;-). Out of the two, I choose the former, as it simply   takes less time.


[/source]
Quote:The first article alone, including comments, is 32 pages long on my 1600x1200 monitor with fairly small text sizes. Reading the whole thing would entail reading every little off topic veer the commentators seem to like making, and every repeat of what's allready been said. Could you try pointing out some of the posts that underscore your point prehaps?

Ooh, that long? I read it during downtime, went fairly quickly. I've summarized the points referred to.

Quote:Lies, lies and deceptions my friend :-) (No, I'm not accusing you of lying, I just like using that phrase - but you are wrong). For a few examples against this, I give you:

My my, by now you are bending backwards pretty far. I wouldn't call that RAII anymore; closer to ScopeGuard (do something, report as finished or automatically clean up at exit). Oh BTW, raii_scope object didn't support that, did it? See how this is ballooning?

Quote:There's no way you can directly return an error code. It's valid C++, although considered bad practice by many, to throw during a destructor*.

Bad practice is mildly put. And the contortions you are going to to rescue the 'return error from dtor' thing are just amazing.

Quote:LIBRARY_IMPLEMENTOR_FAILED_ELEMENTARY_SCHOOL_MATH

hehe :D

Quote:
Quote:Well, no. With RAII built in, you add the complexity and ugliness of registering cleanup functions. No more is the utopic code that didn't appear to have any error handling code at all.

And how is this different from the ugliness of manually cleaning up yourself?

Congratulations, I am exasperated. I myself am saying that error code-style code is visible, while you are claiming "prettiness". Therefore, interesting to see you saying their ugliness is comparable :)

Quote:
Quote:Looks ok. I have no idea though why you insist on throwing in the failure cases where you could just return an error code.

For consistancy. It's the choice between consitantly using error codes or consitantly using exceptions.

Oh saints preserve. Consistency is good, but you violate the very meaning of the word "exceptions" by treating all it-did-not-succeed conditions as such.

And let me tell you: once you actually understand how exceptions are implemented (starting with compiler tracking current objects, up to kernel SEH), you will probably have an entirely different opinion of what it means to throw one.

Quote:<sarcasm>Because normal usage of a checksum checking function is using it on a non existant file</sarcasm>

You'd be surprised. In my current work (100 KLOC, 30 hands on deck), it is commonplace for maps etc. to refer to not-yet-created objects. If you then crap out completely, you are wasting everyone's time until the issue is fixed (there is some question whether this approach or break-immediately is better, but that's beyond our scope here).
I'd say anything that refers to IO must be treated as likely to fail; therefore, exceptions are by definition not warranted unless it's somehow more convenient there.
The try-it-first version (VerifyChecksumIfFileExists) would work, but now we are actually running into performance problems: seeking and opening files is *slow*. Therefore, there is no way around a try-it-and-report-status version, giving the caller the freedom to decide what to do on error.

Quote: I've allready gone over how I fail to see how this catch statement:

Nothing beyond matters of taste, which cannot be debated.

Quote:Right, because WinAmp needs to know wheither or not my computer is on fire.

Indeed. (oh goody, sarcasm as a hedge bet, eh? ;p) When you get a bus error (memory defect), I'd want WinAmp to close gracefully and save my carefully constructed playlist, instead of having the OS nuke everything.
Now picture this: the error happens while loading an mp3, and some idiot wrapped that in a catch(...), thus preventing the SIGBUS or whatnot from reaching the top-level handler. The error is ignored, a later error probably takes down the system, my data is lost and the coder should be run of of town on a rail.

Quote:
Quote:Not when you have to check every new block of memory you allocate to make sure you actually got it.

Disagree. Transactional operation is easier when you cannot be interrupted at any time.

*snort* You're making this out to be a problem? Disagree.
But what does it have to do with transactional programming? Just how often do you hammer your heap, anyway? (side note: thanks to my nemesis Xerces, the game does about 200k allocs during startup. When running BoundsChecker or similar, I spend 30 minutes cursing them).

Quote:So far, I've seen these arguments on your part against RAII:

Correct, not sure if that's exhaustive. But ya know what? This really has been done to death (cf. another thread). If you want to use exceptions everywhere, fine. My comments on their pitfalls and when it makes sense to use error codes are apparently spoken into the wind. I only pray I don't have to use your APIs ;p
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
Quote:Original post by Jan Wassenberg
Quote:The first article alone, including comments, is 32 pages long on my 1600x1200 monitor with fairly small text sizes. Reading the whole thing would entail reading every little off topic veer the commentators seem to like making, and every repeat of what's allready been said. Could you try pointing out some of the posts that underscore your point prehaps?

Ooh, that long? I read it during downtime, went fairly quickly. I've summarized the points referred to.

Quote:Lies, lies and deceptions my friend :-) (No, I'm not accusing you of lying, I just like using that phrase - but you are wrong). For a few examples against this, I give you:

My my, by now you are bending backwards pretty far. I wouldn't call that RAII anymore; closer to ScopeGuard (do something, report as finished or automatically clean up at exit). Oh BTW, raii_scope object didn't support that, did it? See how this is ballooning?


Ehm, no, I don't see. Implementing that extra functionality takes very few extra lines of code. As in, you add this member function:

void release( void ) {    if ( cleaner ) {        cleaner( value );        cleaner = 0;    }}


Which is 6 lines, including single-character-liners. Hardly ballooning into a massive pile of high mantinence code. RAII simply makes sure things get destroyed, rather than forcing a certain order.

Quote:
Quote:There's no way you can directly return an error code. It's valid C++, although considered bad practice by many, to throw during a destructor*.

Bad practice is mildly put. And the contortions you are going to to rescue the 'return error from dtor' thing are just amazing.


Only in the interests of making it completely generic ;-). For me, I prefer to have such things asserted() rather than exception throwing. If a handle is getting repeatedly released, the program is probably OK (or has allready done all the damage it can) and thus throwing an exception serves no purpouse - exceptions are meant to deal with exceptional circumstances, not bugs.

Quote:
Quote:
Quote:Well, no. With RAII built in, you add the complexity and ugliness of registering cleanup functions. No more is the utopic code that didn't appear to have any error handling code at all.

And how is this different from the ugliness of manually cleaning up yourself?

Congratulations, I am exasperated. I myself am saying that error code-style code is visible, while you are claiming "prettiness". Therefore, interesting to see you saying their ugliness is comparable :)


Error code style is both ugly and prone to uncaught errors. Exception code style is either ugly OR places the code elsewhere in the name of prettyness IF directly overlay non-raii code - directly overlaying raii over non-raii code is going to have the ugly associated with the non-raii code. Another possible usage form for raii_scope would be:

raii_scope< HANDLE , & CloseHandle > h( initial_value );

This has the added benifit of allowing one to create a typedef:

typedef raii_scope< HANDLE , & CloseHandle > raii_handle;

raii_handle h ( OpenFile( ... ) );

This means you have a one-liner somewhere that you're going to have to remember exists. Of course, you'd have to remember that there's a one liner that defines HANDLE out there somewhere in order to use the non RAII function, so, I call it pretty comparable in terms of usage :-);.

Quote:
Quote:
Quote:Looks ok. I have no idea though why you insist on throwing in the failure cases where you could just return an error code.

For consistancy. It's the choice between consitantly using error codes or consitantly using exceptions.

Oh saints preserve. Consistency is good, but you violate the very meaning of the word "exceptions" by treating all it-did-not-succeed conditions as such.


Unless you don't program everything to expect to fail, by making your code expect to succeed :-).

Quote:And let me tell you: once you actually understand how exceptions are implemented (starting with compiler tracking current objects, up to kernel SEH), you will probably have an entirely different opinion of what it means to throw one.


I've implemented exceptions in a toy compiler. I hope this qualifies me for the glorious title of knowing one of the many ways that exceptions can be implemented :-).

Quote:
Quote:<sarcasm>Because normal usage of a checksum checking function is using it on a non existant file</sarcasm>

You'd be surprised. In my current work (100 KLOC, 30 hands on deck), it is commonplace for maps etc. to refer to not-yet-created objects.


And then you ask what the name or size of the object is, right?

No?

Refering to something that has yet to exist is one thing. Trying to use something that has yet to exist is another.

Quote:If you then crap out completely, you are wasting everyone's time until the issue is fixed (there is some question whether this approach or break-immediately is better, but that's beyond our scope here).


So don't completely crap out. To throw an exception means "There was something I couldn't deal with, prehaps you can do better". Want to report the double freeing of a handle? Don't throw an exception, open a message dialog and do it! Exceptions arn't the only way to deal with errors, but they do the same job and more than error codes.

Quote:I'd say anything that refers to IO must be treated as likely to fail; therefore, exceptions are by definition not warranted unless it's somehow more convenient there.


Likely to fail at some point yes (couldn't open the file, tried to read past the end of the file, etc), but not likely to fail on any given single read of a byte.

Quote:The try-it-first version (VerifyChecksumIfFileExists) would work, but now we are actually running into performance problems: seeking and opening files is *slow*. Therefore, there is no way around a try-it-and-report-status version, giving the caller the freedom to decide what to do on error.


industry:/home/panda# ls -R -l / > all_files 2>/dev/null... [3 seconds pass]panda@industry:~$ wc -l all_files118040 all_filespanda@industry:~$ _


On my crappy ass 600mhz old server built from the leftover parts of a Compaq, with it's crappy hard drive, it takes 3 seconds to list the permissions, owning group and user, file size, last modified date and time, and filename of each file on my computer. Let's make the extremely conservative estimate that there's only about 50 thousand files on my computer (based off the fact that all_files contains 118 thousand lines) and ignore the fact that the computer also has to format all this information and write it to a file.

By this calculation, my computer can find the sizes of over 16 thousand files in 1 second - or in other words, it takes 16-thousandths of a second to find the size of one file. I hardly call this slow :-).

Quote:
Quote:Right, because WinAmp needs to know wheither or not my computer is on fire.

Indeed. (oh goody, sarcasm as a hedge bet, eh? ;p) When you get a bus error (memory defect), I'd want WinAmp to close gracefully and save my carefully constructed playlist, instead of having the OS nuke everything.


I'd rather the OS nuke everything, and not take the chance of possibly overwriting the playlist I've allready saved (since I've used windows, and word since before it gained the auto-save feature, enough to know to do this every so often or when finished creating something) with invalid jibberish.

Quote:Now picture this: the error happens while loading an mp3, and some idiot wrapped that in a catch(...), thus preventing the SIGBUS or whatnot from reaching the top-level handler. The error is ignored, a later error probably takes down the system, my data is lost and the coder should be run of of town on a rail.


I doubt that'd happen, people are so used to programs crashing and loosing all their data due to bugs that one realizes that one should save every now and then - programs such as Word now have features such as auto-save that help deal with problems that mean the system should shut the heck down.

Quote:
Quote:
Quote:Not when you have to check every new block of memory you allocate to make sure you actually got it.

Disagree. Transactional operation is easier when you cannot be interrupted at any time.

*snort* You're making this out to be a problem? Disagree.
But what does it have to do with transactional programming? Just how often do you hammer your heap, anyway? (side note: thanks to my nemesis Xerces, the game does about 200k allocs during startup. When running BoundsChecker or similar, I spend 30 minutes cursing them).


Sorry, I should've been more generalized. The statement about "check every new block" should be "check every function, every tiny little operation, even every new block or variable declaration."

My point is that if an exception can throw during the middle of a transaction, it's because you have an error occured. Similarly, the same applies if any of the functions return an error code.

A buggy "transactional" function without exceptions might look like:

void transfer( int ammount , const std::string & source_name , const std::string & destination_name ){    account & source = lookup_account( source_name );    source.cash -= ammount;    account & destination = lookup_account( destination_name );    destination.cash += ammount;}


The code fails when we find out that the destination account dosn't exist. Unless we review the code and spot the bug, the only way to catch this is by doing error checking every step of the way, and even then, the error will only be caught at run time, once things are allready partly done - which means I consider this at best a stopgap measure. Now, if lookup_account dosn't return an error code, but throws instead, how could we deal with this?

Step 1: Add a throw() specification. Example:

struct transfer_failed : public std::exception {    transfer_failed( void ) throw() {}    ~transfer_failed( void ) throw() {}    const char * what( void ) const throw() { return "Transfer failure" ; }};void example_transfer( int transfer , const std::string & source_name , const std::string & destination_name ) throw( transfer_failed )


Key point: this should be unique to the function (or possibly a set of overloaded functions), although it can inherit from a more generalized version.

Step 2: Get a compiler error: lookup_account can throw other stuff!!!

Step 3: We now know there's a bug. We have a supposedly transactional function that isn't transactional. We fix it:

void transfer( int ammount , const std::string & source_name , const std::string & destination_name ) throw( transfer_failed ){    account * source;    account * destination;    try    {        //Do stuff that dosn't actually modify the values:        source = & lookup_account( source_name );        destination = & lookup_account( destination_name );    }    catch( account_does_not_exist )    {        throw transfer_failed();    }    source->cash -= ammount ;    destination->cash += ammount ;}


Going by this methodology, I'd actually argue that transactional programming is EASIER with exceptions. If you want to be even safer, use a no-throw guarantee like this:

void do_actual_transfer( int ammount , account & source , account & destination ) throw(){    source.cash -= ammount ;    destination.cash += ammount ;}void transfer( int ammount , const std::string & source_name , const std::string & destination_name ){    account & source = lookup_account( source_name );    account & destination = lookup_account( destination_name );    do_actual_transfer( ammount , source , destination );}


Quote:
Quote:So far, I've seen these arguments on your part against RAII:

Correct, not sure if that's exhaustive. But ya know what? This really has been done to death (cf. another thread). If you want to use exceptions everywhere, fine. My comments on their pitfalls and when it makes sense to use error codes are apparently spoken into the wind. I only pray I don't have to use your APIs ;p


I feel exactly the same with my comments, and hold the same sentiments towards your APIs, I assure you ^_^.

Edit: Finished a sentance, added missing
opening tags.

[Edited by - MaulingMonkey on May 18, 2005 2:07:15 AM]
This degenerated into quite possibly the most professional and polite flamewar I've ever read.
daerid@gmail.com
Quote:Original post by daerid
This degenerated into quite possibly the most professional and polite flamewar I've ever read.


Some people call this a discussion :-). We are expostulating each others opinions with regards to exceptions, holding a forum. Yes, I looked up expostulating in a thesarus. I call it "attempting to broaden my limited vocabulary".

This topic is closed to new replies.

Advertisement