Sign in to follow this  

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

This topic is 4588 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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).

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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 ;)

Share this post


Link to post
Share on other sites
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: [ code ] -> [ source ] + const functions for raii_scope

[Edited by - MaulingMonkey on May 14, 2005 2:38:10 PM]

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
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]

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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_files
118040 all_files
[b]panda@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 [ quote ] opening tags.

[Edited by - MaulingMonkey on May 18, 2005 2:07:15 AM]

Share this post


Link to post
Share on other sites
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".

Share this post


Link to post
Share on other sites
hehe, that's quite a post :) Sadly I do not have the time to continue at such a pace; therefore, only sparse replies:

Quote:
Quote:
See how this is ballooning?

Ehm, no, I don't see

yes, you do: "This means you have a one-liner somewhere that you're going to have to remember exists". What I am pointing out is that (in response to my poking) you keep having to add more functionality/code. Here a RAII class, there a new method, then the report-error-from-dtor monstrosity - tell me with a straight face that it's all conceptually more simple than "if(err != 0) return err;".
I give you a bit of wisdom from Dijkstra: "The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague."

Quote:
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 :-).

Ah, cool. But there's more to the story: look into SEH, which is the backend for VC's mechanism. Then you will know why throwing is so insanely slow.

Quote:
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.

I think you misunderstood. In our RTS game, scenarios often refer to art files that don't exist - possibly because someone forgot to commit all files. Since we're distributed over many time zones, waiting for that problem to be fixed is not an option; therefore, things must not explode when a file is missing.

Quote:
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 :-).

It may actually be less than that (not sure about your benchmark there), but here's the catch: our game has a VFS that needs to enumerate all data files at startup. It organizes them all in a tree, so that file open performance remains fast even if several patch archives and mods have been installed. Windows explorer tells me there are 18181 files in the binaries/ directory, so 16ms for each one would indeed end up being dog slow.

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

Ah. This is why I have refrained from touching on exception-related stuff just now - there's no point ;p

I give you one parting thought, though: imagine you have a exception-infested library. If for some reason exceptions must not reach the caller(*), you are forced to wrap every call site in catch(...) (+). Inconvenient, sloow, and you stomp on unexpected exceptions (access violation due to hardware or OS error). Nice!
Now if the library had been written with error codes (which may or may not be more work, depending on the taste of the programmer), it would be much easier to use. If you insist on getting exceptions when something goes wrong, you can write simple and more efficient if(err == WHATEVER) throw whatever(); wrappers.

* excellent example: when passing across language boundaries.
+ non-sequitur: what do your exception specifications make of the fact that anything can "throw" SEH exceptions?

Share this post


Link to post
Share on other sites
Quote:
Original post by Jan Wassenberg
hehe, that's quite a post :) Sadly I do not have the time to continue at such a pace; therefore, only sparse replies:


Understandable :-).

Quote:
Quote:
Quote:
See how this is ballooning?

Ehm, no, I don't see

yes, you do: "This means you have a one-liner somewhere that you're going to have to remember exists". What I am pointing out is that (in response to my poking) you keep having to add more functionality/code. Here a RAII class, there a new method, then the report-error-from-dtor monstrosity - tell me with a straight face that it's all conceptually more simple than "if(err != 0) return err;".
I give you a bit of wisdom from Dijkstra: "The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague."


It's adding 5 lines of code in one spot which is then reusable everywhere else. From a USERS perspective, it is still the wonderful 1 liner it's allways been, and from that same perspective, it's conceptually simpler. Note that without RAII, if anything needs cleaning up, "if (err != 0) return err" will leak just the same as "if (err != 0) throw exception". So we turn this:

raii_scope< HANDLE , & CloseHandle > handle( CreateFile( ... ) );
if ( err != 0 ) return/throw error/exception;
handle.release();
...


Into:

HANDLE handle = CreateFile( ... );
if ( err == 0 )
{
CloseHandle( handle );
handle = 0; //so we don't doubly close the handle, which would be a big error
...
}
if ( handle != 0 ) CloseHandle( handle );
return err;


Or:

HANDLE handle = CreateFile( ... );
if ( err != 0 )
{
//close all pre-existing resources here:
CloseHandle( handle );
return err;
}
CloseHandle( handle ); handle = 0;
...
return err;


This code can get much much worse in size difference as one adds more handles or error conditions to the mix.

Quote:
Quote:
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 :-).

Ah, cool. But there's more to the story: look into SEH, which is the backend for VC's mechanism. Then you will know why throwing is so insanely slow.


I've looked into it some in the past, and although I'm having difficulty finding any major complaints over it's preformance using google, probably because I'm not using the right terms.

But if we're going to argue preformance, let's view another point.

By returning an error code, and checking this error code on every call, we incur a potentially large overhead for normal operation. This means that, although error codes outpreform exceptions when an error does occur, exceptions outpreform error codes when things succeed, which is the usual case.

This route was most likely chosen because of the fact that it speeds up the normal case. If one wanted, behind the hood, one could implement exceptions in the same way return codes are implemented - which would mean exceptions would preform exactly like return codes!

The fact, however, is that most of the time an error is NOT occuring, means that switching to exceptions can result in a net gain in terms of preformance.

Quote:
Quote:
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.

I think you misunderstood. In our RTS game, scenarios often refer to art files that don't exist - possibly because someone forgot to commit all files. Since we're distributed over many time zones, waiting for that problem to be fixed is not an option; therefore, things must not explode when a file is missing.


Out of curiosity, how do you handle this? Load in a default piece of artwork? That's a method of error handling. One can do that with exceptions too:

struct texture { ... };
struct model { texture texture; };
struct scenario { vector< model > models };
void load_scenario( scenario & scenario , ... )
{
...
for ( ... )
{
model model;
try
{
model.texture = load_model( ... );
}
catch( model_does_not_exist )
{
model.texture = load_model( "placeholder.bmp" );
}
scenario.models.push_back( model );
}
}


Quote:
Quote:
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 :-).

It may actually be less than that (not sure about your benchmark there), but here's the catch: our game has a VFS that needs to enumerate all data files at startup. It organizes them all in a tree, so that file open performance remains fast even if several patch archives and mods have been installed. Windows explorer tells me there are 18181 files in the binaries/ directory, so 16ms for each one would indeed end up being dog slow.


16ms == 16/1,000 of a second (sixteen thousandths of a second)
16ms != 1/16,000 of a second (one sixteen-thousandth of a second, what was meant, although quite badly said)
0.0625ms == 1/16,000 of a second.

Big difference.

At 0.0625ms/file, it'd take 1.13 seconds to check the size of each file in your binaries/ dir.
At 16ms/file, it'd take 4.8 minutes.

Note that my piece of **** of a server can preform the first of the two speeds, so I'm somehow doubtful that any modern computer is going to do it slower than that.

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

Ah. This is why I have refrained from touching on exception-related stuff just now - there's no point ;p

I give you one parting thought, though: imagine you have a exception-infested library. If for some reason exceptions must not reach the caller(*), you are forced to wrap every call site in catch(...) (+). Inconvenient, sloow, and you stomp on unexpected exceptions (access violation due to hardware or OS error). Nice!
Now if the library had been written with error codes (which may or may not be more work, depending on the taste of the programmer), it would be much easier to use. If you insist on getting exceptions when something goes wrong, you can write simple and more efficient if(err == WHATEVER) throw whatever(); wrappers.


inline int dispatch( boost::function< void ( void ) > function )
{
try
{
function();
}
catch( const api_exception & e ) { return e.code(); } //API Exception -> Error Code
catch( std::bad_alloc ) { exit(0); } //EJECT!!! EJECT!!!
catch( ... ) { return -1; }
}

int API_Function( void )
{
return dispatch( do_stuff );
}

int API_Function( int argument )
{
return dispatch( bind( do_stuff , argument ) );
}


Not particularly hard :-). If you want to have your library return error codes, design it so it's easy. Then again, I'm a C++ diehard. I just figure I shouldn't constrain my library just so that C users can use it. The need to target multiple languages with a restricted interface is one to be avoided, in my mind.

Quote:
* excellent example: when passing across language boundaries.
+ non-sequitur: what do your exception specifications make of the fact that anything can "throw" SEH exceptions?


SEH is ignored, since I'm not programming for banking systems, nor if I was would I be programming on a windows platform. I've noticed that there are patches to add SEH to GCC out there, but I've not gone to the bother of installing any, so I don't even have it available. I use signal handlers to deal with SIGHUP and the like.

Signals are high-level events. To deal with SIGHUP I might close and reopen my listening socket (with new parameters) - which isn't something that should interrupt the flow of my other threads (it's a signal not an exception damnit!!!)

To deal with SIGBUS in your WinAmp with a playlist example, I'd have it walk through the playlist and save it to a special "to-be-recovered-from" file. This is a high level action, not something that I should be trying to unwind from a thread of execution to do.

Share this post


Link to post
Share on other sites
Also, just to show that throwing and catching have nothing to do with wheither or not signals will reach their intended target on my sane GCC system:

panda@industry:~$ cat test.cc
#include <signal.h>

#include <exception>
#include <iostream>
#include <string>

bool die = false;

using namespace std;

struct neverdie
{
~neverdie( void )
{
while( !die ) {}
}
};

void sigusr2handler( int )
{
cout << "Recieved: SIGUSR2" << endl;
}

void sigusr1handler( int )
{
cout << "Recieved: SIGUSR1" << endl;
cout << "Setting die to true, any neverdie s will die :-)." << endl;
die = true;
}

void in_a_try( void )
{
cout << "Can we detect a signal within a try block? (Hit ENTER to continue)" << endl;
string line;
getline( cin , line );
}
void in_a_throw( void )
{
cout << "Can we detect a signal while we're throwing? (Send a SIGUSR1 to coninue)" << endl;
neverdie nevereverever;
throw exception();
}
void in_a_catch( void )
{
cout << "Can we detect a signal in a catch statement? (Hit ENTER to continue)" << endl;
string line;
getline( cin , line );
}

int main ( int argc , char ** argv )
{
try
{
signal( SIGUSR1 , sigusr1handler );
signal( SIGUSR2 , sigusr2handler );
in_a_try();
in_a_throw();
}
catch( ... )
{
in_a_catch();
}

}


panda@industry:~$ ./test
Can we detect a signal within a try block? (Hit ENTER to continue)
(CTRL+Z)
[1]+ Stopped ./test
panda@industry:~$ killall -USR2 test
panda@industry:~$ fg %1
./test
Recieved: SIGUSR2

Can we detect a signal while we're throwing? (Send a SIGUSR1 to coninue)
(CTRL+Z)
[1]+ Stopped ./test
panda@industry:~$ killall -USR1 test
panda@industry:~$ fg %1
./test
Recieved: SIGUSR1
Setting die to true, any neverdie s will die :-).
Can we detect a signal in a catch statement? (Hit ENTER to continue)
(CTRL+Z)
[1]+ Stopped ./test
panda@industry:~$ killall -USR2 test
panda@industry:~$ fg %1
./test
Recieved: SIGUSR2

panda@industry:~$ _


(CTRL+Z) == stopping sequence, I could've run the program in the background and had the "Recieved:" messages pop up as soon as I sent off the signal had I wanted to.

Share this post


Link to post
Share on other sites
And to continue on this providing more proof for my arguments streak, the crappy server can manage:

panda@industry:~$ cat test.cc
#include <time.h>

#include <iostream>
using namespace std;

unsigned int i;

int main ( void )
{
clock_t begin = clock();
clock_t end = begin;
while ( end - begin < CLOCKS_PER_SEC * 10 )
{
try
{
throw std::exception();
}
catch( std::exception e )
{
++i;
}
end = clock();
}
float time = ((float)(end - begin)) / CLOCKS_PER_SEC;
cout << "Time elapsed: " << time << " seconds" << endl;
cout << "Throws caught: " << i << endl;
cout << "Throws/second: " << i/time << endl;
}


panda@industry:~$ ./test
Time elapsed: 10 seconds
Throws caught: 862242
Throws/second: 86224.2


Notes: compiled in debug mode, could set a breakpoint on exception throw if wanted, benchmark is on 600mhz piece of shit server using GCC 3.3.5, think that's about it.

Share this post


Link to post
Share on other sites
I think both methods have merrits.
For exception handling I like that it cxan simply things by catching all errors without having to write specific code for each function call. However I dislike that during debugging (or otherwise) it can be difficult to work out where the exception was thrown from.
Exceptions aren't a nice way to handle non-critical errors (not that this is what they are meant for). If you have:
if (!AttemptToDoThis())
if (!AttemptToDoAnotherThing())
if (!AttemptToDoYetAnotherThing())
if (!AttemptToDoOneMoreThing())
OhCrapWereGoingToDie();
YayWeSucceededAtSomeLevel();
Both have their place. In some cases either is appropriate, but in other cases it would be wrong to use the least appropriate one.
I'm sure you'll both agree that when working with code that uses strictly one or the other, switching to the other means lots of "if (FAILED(hr)) _com_issue_error(hr);" type of stuff, or lots of "try { doX(); } catch () { return MY_ERR_CODE; }" which is bad.

Hey, and forget using MS Word in your arguments. It only has the auto-save to make up for it's own bugs and inadequacies. It wouldn't need that feature if it wasn't prone to crashing or bringing down the whole system.

Share this post


Link to post
Share on other sites
Quote:
So we turn this: [..] Into: [mess]

*sigh* no, no, no. The C idiom is a "bailout ladder":

err = do1()
if(err < 0)
goto exit
err = do2()
if(err < 0)
goto exit_undo1

return 0; // success

exit_undo1:
undo1();
exit:
return err;

Duplicating undo1() everywhere sucks, IMO.

Quote:
By returning an error code, and checking this error code on every call, we incur a potentially large overhead for normal operation. This means that, although error codes outpreform exceptions when an error does occur, exceptions outpreform error codes when things succeed, which is the usual case.[..]most of the time an error is NOT occuring, means that switching to exceptions can result in a net gain in terms of preformance.

heh, no way. Quoting a previous post:
Each routine returns an int (or: signed integral value), and has an err variable that is ideally mapped to the eax register (whee, I'm being IA-32 specific). Calls are made via a TRY macro that does err = func(); if(err < 0) return err; .
On the hardware side, this has negligible overhead - the error value isn't moved at all, since it's already returned in eax. Comparison and well-predicted, branch-not-taken costs 1 clock; if the compiler is really clever, it would set all return values via instructions that have flag side-effects, so even the comparison is eliminated.
Now that's somewhat utopic, but what I'm trying to point out is that "testing each return value" is a matter of wrapping each call in a macro, and costs 1-2 instructions | clocks. If resources are allocated, a bailout ladder would be used.
--
How does VC implement exceptions? They basically log creation/destruction of each object, so there's your nothing-was-thrown overhead right there.

Quote:
Out of curiosity, how do you handle this? Load in a default piece of artwork? That's a method of error handling. One can do that with exceptions too:

Right, what I am proposing is policy and unrelated to mechanism (return or exception). BTW, you forgot error checking in the second load_model -> boom. What this example was trying to point out: this is a commonplace occurence and therefore not "exceptional".

Quote:
16ms != 1/16,000 of a second (one sixteen-thousandth of a second, what was meant, although quite badly said)
0.0625ms == 1/16,000 of a second.

Ah, heh. Yeah, 16ms was a bit high. But 62µs is in turn a bit low; VC's stat() is much slower..

Quote:
Note that my piece of **** of a server can preform the first of the two speeds, so I'm somehow doubtful that any modern computer is going to do it slower than that.

.. but anyway: 1130ms at startup is absolutely unacceptable for this task.

Quote:
Not particularly hard :-)

I do not believe you solved the intercept-SEH-exception problem, nor the performance impact of using exceptions for all error reporting.

Quote:
Then again, I'm a C++ diehard. I just figure I shouldn't constrain my library just so that C users can use it. The need to target multiple languages with a restricted interface is one to be avoided, in my mind.

I see. Diehard, eh? That explains the "zealot mind-filter" ;p
Clearly you have different requirements. At work we have a mixed Python-C++ system (a very happy marriage - productivity and speed); the abovementioned RTS game is scripted with javascript. In both cases, tossing exceptions left and right is simply not possible.

Quote:
To deal with SIGBUS in your WinAmp with a playlist example, I'd have it walk through the playlist and save it to a special "to-be-recovered-from" file. This is a high level action, not something that I should be trying to unwind from a thread of execution to do.

High-level, eh? When crashing, all that matters is that the user's data is written out. If phrases like 'high level' get in the way, I would hold the developer criminally negligible.

Quote:
Also, just to show that throwing and catching have nothing to do with wheither or not signals will reach their intended target on my sane GCC system:

That's great, but irrelevant - unless you are writing off all Windows systems.

Quote:
Throws/second: 86224.2

Only 7000 clocks? Not bad at all; GCC/Linux exception implementation is clearly more efficient. Do not forget, though, that there is no unwinding going on, nor are there any objects that need to be destroyed. Real-world exceptions will be much slower.

iMalc:
Quote:
I think both methods have merrits.

Finally someone with sense :) Agree on all points.

Quote:
It only has the auto-save to make up for it's own bugs and inadequacies. It wouldn't need that feature if it wasn't prone to crashing or bringing down the whole system.

Amen to that. *Relying on* such a band-aid to protect user data is the hallmark of shoddy software.
An interesting parallel is the German Torpedo Bureau in the second World War: after repeated torpedo failures, those responsible were found negligent and sentenced to prison terms.

Share this post


Link to post
Share on other sites
Quote:
Original post by iMalc
I think both methods have merrits.
For exception handling I like that it cxan simply things by catching all errors without having to write specific code for each function call. However I dislike that during debugging (or otherwise) it can be difficult to work out where the exception was thrown from.


I have absolutely no problem with this, using GDB, because I can set the program to break on any exception or specific exception. Examples:

(gdb) catch throw
(gdb) catch throw std::exception

Then, the program will stop the moment that type of exception is thrown. I can then preform a backtrace:

(gdb) bt

Which will then list the call stack, complete with line numbers if I so desire.

Quote:
Hey, and forget using MS Word in your arguments. It only has the auto-save to make up for it's own bugs and inadequacies. It wouldn't need that feature if it wasn't prone to crashing or bringing down the whole system.


Heh. Okay, so save-when-crash ;-).

Share this post


Link to post
Share on other sites
Quote:
Original post by Jan Wassenberg
Quote:
So we turn this: [..] Into: [mess]

*sigh* no, no, no. The C idiom is a "bailout ladder":


So:

    HANDLE handle = CreateFile( ... );
if ( err != 0 ) goto undo1;
CloseHandle( handle ); //e.g. example contrived handle.release()
handle = 0; //so we don't double close
...
undo1:
if ( handle != 0 ) CloseHandle( handle );
return err;


Instead of:

raii_scope< HANDLE , & CloseHandle > handle( CreateFile( ... ) );
if ( err != 0 ) return/throw error/exception;
handle.release();
...


The second, RAII version is still smaller.

Quote:
Quote:
By returning an error code, and checking this error code on every call, we incur a potentially large overhead for normal operation. This means that, although error codes outpreform exceptions when an error does occur, exceptions outpreform error codes when things succeed, which is the usual case.[..]most of the time an error is NOT occuring, means that switching to exceptions can result in a net gain in terms of preformance.

heh, no way. Quoting a previous post:
Each routine returns an int (or: signed integral value), and has an err variable that is ideally mapped to the eax register (whee, I'm being IA-32 specific). Calls are made via a TRY macro that does err = func(); if(err < 0) return err; .
On the hardware side, this has negligible overhead - the error value isn't moved at all, since it's already returned in eax. Comparison and well-predicted, branch-not-taken costs 1 clock; if the compiler is really clever, it would set all return values via instructions that have flag side-effects, so even the comparison is eliminated.
Now that's somewhat utopic, but what I'm trying to point out is that "testing each return value" is a matter of wrapping each call in a macro, and costs 1-2 instructions | clocks. If resources are allocated, a bailout ladder would be used.


This is all good argument, but it simply states that error codes arn't much slower, rather than arn't slower period :-). If for god knows why I'm forced to have error checking in an innermost loop which happens to be a bottleneck, 1 cycle can add up.

Quote:
How does VC implement exceptions? They basically log creation/destruction of each object, so there's your nothing-was-thrown overhead right there.


Well, sounds like VC sucks ;-). But seriously, that's an argument against VC's implementation of exceptions, not exceptions in general.

Quote:
Quote:
Out of curiosity, how do you handle this? Load in a default piece of artwork? That's a method of error handling. One can do that with exceptions too:

Right, what I am proposing is policy and unrelated to mechanism (return or exception). BTW, you forgot error checking in the second load_model -> boom.


I intentionally left it out, actually. If there's no placeholder.bmp, then something's seriously messed up and needs looking into. Worst case scenario, it was just pure coincidence, programmer plays around in paint for 5 seconds, and saves placeholder.bmp :-).

Quote:
What this example was trying to point out: this is a commonplace occurence and therefore not "exceptional".


Commonplace for a project does not mean commonplace on a per-call level. If a function is run 1 million times each time, and on average, one of those calls fails, then it's commonplace for that project to have that occur. On the other hand, from the function's perspective, it's an extremely rare exceptional circumstance which only happens 1 in a million times.

Quote:
Quote:
16ms != 1/16,000 of a second (one sixteen-thousandth of a second, what was meant, although quite badly said)
0.0625ms == 1/16,000 of a second.

Ah, heh. Yeah, 16ms was a bit high. But 62µs is in turn a bit low; VC's stat() is much slower..


Either VC's fault, your hard drive's fault, or your file system's fault. For either of the latter two, this will likely be offset partially as the file is cached in memory, assuming one accesses it.

Quote:
Quote:
Note that my piece of **** of a server can preform the first of the two speeds, so I'm somehow doubtful that any modern computer is going to do it slower than that.

.. but anyway: 1130ms at startup is absolutely unacceptable for this task.


Assuming none of this is time is from actually waiting on reading from the hard drive, data which will be cached, and thus the penalty incurred only once when reading.

Quote:
Quote:
Not particularly hard :-)

I do not believe you solved the intercept-SEH-exception problem, nor the performance impact of using exceptions for all error reporting.


One problem at a time, eh? :-).

Quote:
Quote:
Then again, I'm a C++ diehard. I just figure I shouldn't constrain my library just so that C users can use it. The need to target multiple languages with a restricted interface is one to be avoided, in my mind.

I see. Diehard, eh? That explains the "zealot mind-filter" ;p


Diehard in the sense that my requirements are for myself, and that I'm not going to limit myself to a reduced functionality in an extremely versitile language. I havn't found real problems reimplementing the functionality found in other languages - it might be more verbose (functors for lambada, weird crazy ass templates for smalltalkesque syntax) but it works :-).

Quote:
Clearly you have different requirements. At work we have a mixed Python-C++ system (a very happy marriage - productivity and speed); the abovementioned RTS game is scripted with javascript. In both cases, tossing exceptions left and right is simply not possible.


In a multi-language environment I could see this being more of a problem. I have high enough productivity and flexibility in C++ that I can adapt the padigrams of other languages, rather than depending on actually having the language to use it.

Quote:
Quote:
To deal with SIGBUS in your WinAmp with a playlist example, I'd have it walk through the playlist and save it to a special "to-be-recovered-from" file. This is a high level action, not something that I should be trying to unwind from a thread of execution to do.

High-level, eh? When crashing, all that matters is that the user's data is written out. If phrases like 'high level' get in the way, I would hold the developer criminally negligible.

It wouldn't get in the way, because I'd use the sane way to deal with things - a signal handler.

Quote:
Quote:
Also, just to show that throwing and catching have nothing to do with wheither or not signals will reach their intended target on my sane GCC system:

That's great, but irrelevant - unless you are writing off all Windows systems.


Unless, of course, the example compiles on Windows. Which it does.

Quote:
Quote:
Throws/second: 86224.2

Only 7000 clocks? Not bad at all; GCC/Linux exception implementation is clearly more efficient. Do not forget, though, that there is no unwinding going on, nor are there any objects that need to be destroyed. Real-world exceptions will be much slower.


You have unwinding with your bailout ladder, too. Real-world exceptions will also have optimizations turned on.

Quote:
iMalc:
Quote:
I think both methods have merrits.

Finally someone with sense :) Agree on all points.

Quote:
It only has the auto-save to make up for it's own bugs and inadequacies. It wouldn't need that feature if it wasn't prone to crashing or bringing down the whole system.

Amen to that. *Relying on* such a band-aid to protect user data is the hallmark of shoddy software.


Or shoddy wiring. Or shoddy environment (read: Windows). Or that annoying little brother. Or your cat, if it likes to chew on wires. There's half a bazillion things that can go wrong which can cause you to loose a file, after all :-). Of course I won't deny that Word is one of the epitomies (sp?) of bad software, but that "band-aid" works just as well for hardware failures and OS bugs, which is where any sort of after-the-fact system is going to have a big chance to fail.

Quote:
An interesting parallel is the German Torpedo Bureau in the second World War: after repeated torpedo failures, those responsible were found negligent and sentenced to prison terms.


Yes, because we're programming torpedos </sarcasm>.

Share this post


Link to post
Share on other sites
Quote:
The second, RAII version is still smaller.

Duh! Are you surprised?

Quote:
This is all good argument, but it simply states that error codes arn't much slower, rather than arn't slower period :-). If for god knows why I'm forced to have error checking in an innermost loop which happens to be a bottleneck, 1 cycle can add up.

*sigh* You really think exceptions are faster? I mean, that's just amazing. For someone who has implemented them, you really have no clue.

Quote:
Well, sounds like VC sucks ;-). But seriously, that's an argument against VC's implementation of exceptions, not exceptions in general.

I was under the impression that this was the standard approach. See the end of the other thread, which gives performance and implementation data.

Quote:
I intentionally left it out, actually. If there's no placeholder.bmp, then something's seriously messed up and needs looking into. Worst case scenario, it was just pure coincidence, programmer plays around in paint for 5 seconds, and saves placeholder.bmp :-).

Riight.
I've had bugs in my Zip IO code (part of VFS) that only failed on files with certain offsets. You do not want such a bug to take down the entire game. Which it would here, since that may throw an exception which you don't catch (=> boom).

Quote:
Commonplace for a project does not mean commonplace on a per-call level. If a function is run 1 million times each time, and on average, one of those calls fails, then it's commonplace for that project to have that occur. On the other hand, from the function's perspective, it's an extremely rare exceptional circumstance which only happens 1 in a million times.

It's great that you can make everything seem small by pulling "million" out of your hat. Point is taken, but when writing a library/code module, you can't assume exactly how often you will be called. It seems quite arrogant to say "all errors are exceptions".

Quote:
One problem at a time, eh? :-).

hehe. Well, as we stand, these problems leave your exception->error thunks not suitable for production use.

Quote:
Diehard in the sense that my requirements are for myself, and that I'm not going to limit myself to a reduced functionality in an extremely versitile language. I havn't found real problems reimplementing the functionality found in other languages - it might be more verbose (functors for lambada, weird crazy ass templates for smalltalkesque syntax) but it works :-).

Let me guess - you really only use (i.e. know well enough) C++? Now obviously all non-toy languages are Turing complete, so anything you can do in one you can emulate in another. But that does not address the fact that certain tasks are much easier in higher-level languages.

Quote:
I have high enough productivity and flexibility in C++ that I can adapt the padigrams of other languages, rather than depending on actually having the language to use it.

"*Enough* productivity and flexibility", heh. Someone using assembly language could say that of themselves. Now it's great to adapt useful things from other languages, but you can't say with a straight face that Python offers no advantages over C++.

Quote:
It wouldn't get in the way, because I'd use the sane way to deal with things - a signal handler.

Oops, those aren't raised on Windows. What do you do then?

Quote:
Quote:
Quote:
Also, just to show that throwing and catching have nothing to do with wheither or not signals will reach their intended target on my sane GCC system:

That's great, but irrelevant - unless you are writing off all Windows systems.

Unless, of course, the example compiles on Windows. Which it does.

You misunderstand. Since signals are not the delivery vehicle for access violation notifications on Windows, whether or not they arrive is irrelevant.

Quote:
You have unwinding with your bailout ladder, too. Real-world exceptions will also have optimizations turned on.

Duh.

Quote:
There's half a bazillion things that can go wrong which can cause you to loose a file, after all :-).

What does that have to do with our discussion?! But hey, if a bug destroys user work, at least you can tell them: "maybe your cat would have also caused it".

Quote:
but that "band-aid" works just as well for hardware failures and OS bugs, which is where any sort of after-the-fact system is going to have a big chance to fail.

This obviously went right past you. A safety net is great, but you still don't want to *rely on* (<- did you spot the emphasis this time?) it by being careless on the tightrope.

Quote:
Yes, because we're programming torpedos </sarcasm>.

Sarcasm is not an adequate reply. And speak for yourself - if at work we screw up, people run into walls (ouch) and bang up a rather expensive helmet.
It would do the industry good to assume more responsibility than "manajmint not responsible fer nuthin'".

Share this post


Link to post
Share on other sites
Quote:
Original post by Jan Wassenberg
Quote:
The second, RAII version is still smaller.

Duh! Are you surprised?

Point is, if we're going to talk about the horrible ballooning effects of adding 5 lines of code elsewhere, we shouldn't ignore the fact that we're saving 5 lines of code in two uses as well.

Quote:
Quote:
This is all good argument, but it simply states that error codes arn't much slower, rather than arn't slower period :-). If for god knows why I'm forced to have error checking in an innermost loop which happens to be a bottleneck, 1 cycle can add up.

*sigh* You really think exceptions are faster? I mean, that's just amazing. For someone who has implemented them, you really have no clue.

You said it yourself ;-). It dosn't even have to be in an innermost loop, if every single function call you make has return codes to keep track of. Is the difference small enough that it's not going to be a main arguing point? Probably. Is the difference small enough that it won't offset the overhead during a throw, making the argument a moot point? That will depend on usage...

Quote:
Quote:
Well, sounds like VC sucks ;-). But seriously, that's an argument against VC's implementation of exceptions, not exceptions in general.

I was under the impression that this was the standard approach. See the end of the other thread, which gives performance and implementation data.


Program         Code Size    Time (no throws)   Time (with throws)	
XBench.c 4.6k 1392 ms 1362 ms
CPPBench.cpp; 35.3k 1492 ms 71343 ms
Of course, you just took the code without checking that these benchmarks were on a decent compiler and recent. Compiling them myself, we get:
CPPBench.cpp;   35.3k          47 ms             1140 ms
Notes:

1) This is in release mode, exceptions preform the same either way, nothrow preforms faster in release.
2) This benchmark compares running normally every time to throwing an exception every time. Exceptions are not meant to fail 25 billion times a frame, this is not where they excell. Even so, they only preform 25x slower than a simple divide, a few adds, and a return, when we're re-entering our try/catch block, throwing an exception, and appropriately catching it every frame!
3) Yes, I've allready admitted that exceptions preform slower when they allways fail. For fair comparison, one must look at how often the operation will fail, and compare that to the speed gained when they don't fail.

Quote:
Quote:
I intentionally left it out, actually. If there's no placeholder.bmp, then something's seriously messed up and needs looking into. Worst case scenario, it was just pure coincidence, programmer plays around in paint for 5 seconds, and saves placeholder.bmp :-).

Riight.
I've had bugs in my Zip IO code (part of VFS) that only failed on files with certain offsets. You do not want such a bug to take down the entire game. Which it would here, since that may throw an exception which you don't catch (=> boom).


It'd be caught in my main handler, like all such "missed" exceptions are. If you'd rather replace it with a whitespace texture, and maybe adding a log entry, then do that. Me? If the bitmap that clearly indicates a resource is missing is in and of itself missing, I want the fact uncovered, rather than running around prehaps in my rendering code wondering why the screen is all white, when in fact some goofball thought it'd be funny to not commit ANY of the textures of a scenario someday.

Quote:
Quote:
Commonplace for a project does not mean commonplace on a per-call level. If a function is run 1 million times each time, and on average, one of those calls fails, then it's commonplace for that project to have that occur. On the other hand, from the function's perspective, it's an extremely rare exceptional circumstance which only happens 1 in a million times.

It's great that you can make everything seem small by pulling "million" out of your hat. Point is taken, but when writing a library/code module, you can't assume exactly how often you will be called.
But you can make educated guesses. Considering how much of my code never throws period, 1 in a million would be a conservative measure.
Quote:
It seems quite arrogant to say "all errors are exceptions".
Not all are, but the ones you don't deal with are. If you don't want it to be an exception, head it off at the source. Check that a file exists before trying to open it, and whine in a dialog for the user when it dosn't. Whine again when he types in 1.2.3.4.5 for an IP address. If you've tried to open a file that dosn't exist, it's obvious you havn't planned for that contingency, and therefor, this problem is unexpected - which is when I throw an exception. If this causes 20 billion exceptions to be called a second, then that's a bug that needs fixing, because it's become the rule rather than the exception.

Quote:
Quote:
One problem at a time, eh? :-).

hehe. Well, as we stand, these problems leave your exception->error thunks not suitable for production use.

Which is why I don't use them. For dtor errors, I simply log them and/or assert them.
Quote:
Quote:
Diehard in the sense that my requirements are for myself, and that I'm not going to limit myself to a reduced functionality in an extremely versitile language. I havn't found real problems reimplementing the functionality found in other languages - it might be more verbose (functors for lambada, weird crazy ass templates for smalltalkesque syntax) but it works :-).

Let me guess - you really only use (i.e. know well enough) C++? Now obviously all non-toy languages are Turing complete, so anything you can do in one you can emulate in another. But that does not address the fact that certain tasks are much easier in higher-level languages.


I've programmed in many flavors of BASIC (Q, VB, the "real" kind on the apple 2 - built a tanks game with destructable terrain using the first one), x86 assembly, plenty of different shells, XSL (if that counts), PHP (written online file managers using it), and would be programming in smalltalk probably right now if I hadn't simply ripped off some of the ideas and ported them to C++, where my pre-existing library is.

I feel that I can approach C++ as one of the highest-level languages out there. When I'm missing a feature, I can implement it. Array slicing? boost::range. Lambada? functors or boost::lambada. N-greater-than-single-dynamic-dispatch? Hack out some of the type macros from the boost::function source and do it. Signals and slots? Again, boost.

Quote:
I have high enough productivity and flexibility in C++ that I can adapt the padigrams of other languages, rather than depending on actually having the language to use it.

"*Enough* productivity and flexibility", heh. Someone using assembly language could say that of themselves. Now it's great to adapt useful things from other languages, but you can't say with a straight face that Python offers no advantages over C++.[/quote]

Wrong emphisis, and no I can't. At the same time, I can't say Python offers enough advantages over C++ and PHP for me to not use one of them. Even BASIC has it's advantages. You don't see me writing it anymore though, do you?

Quote:
Quote:
It wouldn't get in the way, because I'd use the sane way to deal with things - a signal handler.

Oops, those aren't raised on Windows. What do you do then?

Quote:
Quote:
Quote:
Also, just to show that throwing and catching have nothing to do with wheither or not signals will reach their intended target on my sane GCC system:

That's great, but irrelevant - unless you are writing off all Windows systems.

Unless, of course, the example compiles on Windows. Which it does.

You misunderstand. Since signals are not the delivery vehicle for access violation notifications on Windows, whether or not they arrive is irrelevant.


I was under the impression that signals still worked. My compile test proves me right:

#include <signal.h>

#include <iostream>

using namespace std;

#define SIGSEGV 11 //C


void handler_sigsegv( int )
{
cout << "We caught a SIGSEGV, aka Segfault or Access Violation" << endl;
}

int main ( int argc , char ** argv )
{
signal( SIGSEGV , handler_sigsegv );
cout << "About to access 0x00000000..." << endl;
*((int *)0x00000000) = 0;
cout << "Accessed 0x00000000!!!" << endl;
}

C:\eclipse\workspace\test.2>Debug\test.2
About to access 0x00000000...
We caught a SIGSEGV, aka Segfault or Access Violation

C:\eclipse\workspace\test.2>_


Funny, that.

Quote:
Quote:
You have unwinding with your bailout ladder, too. Real-world exceptions will also have optimizations turned on.

Duh.


So, same overhead, and converging times since the difference between them remains a constant.

Quote:
Quote:
There's half a bazillion things that can go wrong which can cause you to loose a file, after all :-).

What does that have to do with our discussion?! But hey, if a bug destroys user work, at least you can tell them: "maybe your cat would have also caused it".

*sigh* I'm simply trying to point out that auto-save is a valid technique for helping prevent user data loss. No, your program should preferibly not be buggy, but I have no faith in Microsoft to be able to pull that off, and if they're going to give me a buggy piece of ****, I'd much prefer it have auto-save stuck on as a bandaid, which will also protect against said cat, than just loose my work every 5 minutes.

Quote:
Quote:
but that "band-aid" works just as well for hardware failures and OS bugs, which is where any sort of after-the-fact system is going to have a big chance to fail.

This obviously went right past you. A safety net is great, but you still don't want to *rely on* (<- did you spot the emphasis this time?) it by being careless on the tightrope.

Being careless on the tightrope is stupid, yes. It's even stupider to assume that you're a god with no need for a safety net.
Quote:
Quote:
Yes, because we're programming torpedos </sarcasm>.

Sarcasm is not an adequate reply. And speak for yourself - if at work we screw up, people run into walls (ouch) and bang up a rather expensive helmet.
It would do the industry good to assume more responsibility than "manajmint not responsible fer nuthin'".

Oh I'll agree with that. But the point remains, I'm not programming torpedos, You (havn't mentioned any projects where you) are programming torpedos, and my Cat dosn't exist, so even if he were a super-kitty, he wouldn't be programming no torpedos neither ;-).

Rely on bandaids? I wouldn't, because it's a bad idea.
Don't use a safey net because it's a bandaid? I wouldn't do that either.

There's a difference between not working on bug fixing because you've got a safety net, and realizing the fact that you're only human. No, I think it'd be pretty stupid to rely on having auto-save exist for the sole purpouse of dealing with that bug you couldn't track down. But yes, I think it'd be a solution to hardware failures (one of the many things that can send out signals). If you want to set up a signal handler to deal with specialized contigencies, the more power to you. Just realize that you can't plan for, and deal with, every one outside of a controlled environment.

Share this post


Link to post
Share on other sites
You have utterly disqualified yourself by giving only the C++ portion of the benchmark results on your system. What, you want the C++ version to reflect your newer compiler and faster test system, but not C? What I'd like to know is whether this is due to bad faith or ignorance ("Heinlein's razor"). Congratulations either way.

No less ridiculous is that in reply to "Since signals are not the delivery vehicle for access violation notifications on Windows, whether or not they arrive is irrelevant" you say:
Quote:
I was under the impression that signals still worked. My compile test proves me right
No comment needed.

Semi-related side note: you are hard to take seriously when speaking repeatedly of "Lambada".

Quote:
Yes, I've allready admitted that

It is regrettable you are speaking of "admitted"; after all, this is not a cross-examination. Got something to prove, eh? What place does ego have here?

Quote:
I feel that I can approach C++ as one of the highest-level languages out there.

Get a clue.

Quote:
I'm not programming torpedos

Good. Anyway, the Torpedo Bureau thing was billed as an *analogy*.

I'm not even going to mention the safety net thing, because it seems to be hopelessly beyond you. Try reading exactly what I said again.

But hey, these individual things add up to: my mistake. (recognize yourself?) Since this discussion is not bringing us forward, no further replies will be forthcoming.

Share this post


Link to post
Share on other sites
Quote:
Original post by Jan Wassenberg
You have utterly disqualified yourself by giving only the C++ portion of the benchmark results on your system.


Wait, what? I was simply trying to point out that the original benchmark was outdated. If you look at the C version, you'll notice that in fact the "throwing" version IS faster allready. It's using an exception mechanism, albiet an extremely crude one.

Quote:
What, you want the C++ version to reflect your newer compiler and faster test system, but not C? What I'd like to know is whether this is due to bad faith or ignorance ("Heinlein's razor"). Congratulations either way.


I simply focused on the language I used. I'm well aware the C version will also run faster on my system, I'm not going to make the claim "and so we see the C++ version is obviously faster" - that'd take a good deal of faulty brain cells to assume.

Quote:
No less ridiculous is that in reply to "Since signals are not the delivery vehicle for access violation notifications on Windows, whether or not they arrive is irrelevant" you say:
Quote:
I was under the impression that signals still worked. My compile test proves me right
No comment needed.


Sorry, I was under the impression that you were arguing that they did not in fact work, and the arrival reffered to was the delivery of the message, whereas your prelude refered to the fact that signals would not work (or this is how I read your argument).

I'm confused because first I stated that "my sane GCC system" will reach their intended target - the signal handler, you counterargued that this writes off all windows systems, to which I pointed out that the example compiled on windows just fine, to which you counterargued something about it being irrelevant due to the fact that "signals are not the delivery vehicle for access violation noficiations on Windows". I assumed (since I wasn't exactly sure what you meant with the last bit of your sentance) that you meant the access violation would not trigger the signal handler - so I provided an example which proved that an access violation could indeed be caught via signal handler.

Obviously I'm missing something, so instead of saying what's irrelevant here, can we state what IS relevant?

In any case, I believe my point still stands on this issue - you don't need to deal with SEH directly with the exceptions mechanism.

Quote:
Semi-related side note: you are hard to take seriously when speaking repeatedly of "Lambada".


Lambda, Lambada, same diff :-p. I have the annoying tendancy to speed-read words, "hear" them in my brain, then forever start using that spelling.

Quote:
Quote:
Yes, I've allready admitted that

It is regrettable you are speaking of "admitted"; after all, this is not a cross-examination. Got something to prove, eh? What place does ego have here?


... huh?

I'm quite confused as to what the heck you're trying to say. Are you saying my manhood is small? Is that it?

(Seriously though... "huh?")

Quote:
Quote:
I feel that I can approach C++ as one of the highest-level languages out there.

Get a clue.


Show me an example of something I could not do in C++ and then we'll argue about wheither or not I need a clue.

Quote:
Quote:
I'm not programming torpedos

Good. Anyway, the Torpedo Bureau thing was billed as an *analogy*.

I'm not even going to mention the safety net thing, because it seems to be hopelessly beyond you. Try reading exactly what I said again.

But hey, these individual things add up to: my mistake. (recognize yourself?) Since this discussion is not bringing us forward, no further replies will be forthcoming.


A shame, although considering personal attacks seemed to consist of most of your counter-reply this time around, prehaps it is for the best. I'll post a quick review of the points I believe were covered on in my next post.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Quote:
from the linked article:
(NOTE: This is an installment in my “Handling Online Vermin” series about addressing people with poor online communication habits...


Neither of you have "poor online communication habits", so that article is irrelevant to both of you.

Interesting thread. But why did some very intelligent people go to such great lengths to place exceptions into the language if they are not a good solution to a problem, Jan? (And from what I can tell, they _are_ a good solution to a problem. They don't slow down your code _unless_ something exceptional happens. And oddly enough, I have seen situations where placing a 'try - catch' block around something actually made it go slightly Faster, although I don't remember if that was only in Debug or what - it was a long time ago.)

David

Share this post


Link to post
Share on other sites
Okay, this list is obviously going to be biased by my opinion on exceptions (that they are good), but I think some of the points raised are informative if nothing else. They're also extremely sumerized. Hopefully, those reading may gain some nuggets of information - such as using the language-level nothrow guarantee (throw()) to help prevent bugs in transactional operations.


Argument: RAII dosn't give control over making sure dependancies are destroyed before "this".
MM Counter: Reverse-order-of-construction should ensure this
Jan Counter: Reverse order is not allways appropriate
MM Counter: Then provide an explicit close() as per std::auto_ptr (see release()) and std::_fstream.
Jan Counter: That's not RAII
MM Counter: I, and most of the sources I've read say it is.


Argument: RAII requires you to mantain more classes
MM Counter: Well, 1 reusable class minimum: raii_scope<>
Jan Counter: Your class is ballooning (RE: adding of release())
MM Counter: No, it still reduces usage code, meaning less lines overall.


Argument: Majority of programmers of today suck, they can't write exception safe code.
MM Counter: Can't expect them to write error-checking code, or even buffer-overflow-safe code anyways.


Argument: RAII requires you to mantain documentation (RE: Exception guarantees)
MM Counter: Only as much as exceptionless code with error checking would - e.g. you could have strong/weak/noerror guarantees,
MM Counter: just like strong/weak/no exception guarantees.


Argument: Exceptions can be thrown anywhere (in 2+2)
MM Counter: Fault of the library(s) and/or C++ operator overloading mechanism. Argument against that rather than exceptions.
MM Counter: With operator overloading, you can set error codes that would need to be checked for "proper" execution all the same.


Argument: Transactional programming easier with error codes
MM Counter: Error codes will only catch errors at run time. No throw guarantee at language level (e.g. throw()) can be used to catch bugs
MM Counter: that could cause a transaction to fail at runtime and only be caught then.


Argument: Exceptions are slow
MM Counter: Don't use them for the rule rather than the exception, then.
MM Counter: 86K caught exceptions/second on crappy server
Jan Counter: MSDN Benchmark (catching much slower than normal execution)
MM Counter: MSDN Benchmark outdated (reposted C++ results on recent system)
MM Counter: Exceptions arn't meant to be thrown every frame - throwing an exception is slower than returning an error code.
Jan Counter: You fool, why havn't you included the C benchmark as well
MM Counter: ?!?


Argument: Exceptions are slow
MM Counter: Exceptions can be faster for normal flow (e.g. no exceptions thrown) than return codes
Jan Counter: by maybe 1 cycle.
MM Counter: It adds up when it occurs with every function call
MM Counter: (after the fact) : Also, only general solution is GetLastError, which will be more than 1 cycle likely.


Argument: Throwing C++ Exceptions is slow.
MM Counter: 86K caught exceptions/second on crappy server
Jan Counter: ...slower than return codes.
MM Counter: No contest, but return codes slower for normal use.


Argument: SEH Exceptions thrown by anything
MM Counter: Don't use SEH, use signal handlers.
Sidenote: Signal handlers will catch access violations.
Jan Counter: MM: Not exactly sure? Seemed to be pointing out that they wouldn't work, but apparently I'm missing the argument here...


Argument: Pre-empt error conditions by checking (e.g. if it dosn't matter if a file exists or not, check if_exists(...) before trying to open it which would cause an exception ("slow").
Jan Counter: Too slow.
MM Counter: 16,000 files/second size checked benchmark
Jan Counter: VC's stat() slower
MM Counter: Fault of HD, filesystem, or VC.
Jan Counter: Still, 1 second is too much for my binaries/ directory.
MM Counter: Most of 1 second latency probably from looking up file data, which will be cached assuming you then try and open the file, so overhead is 1-time occurance and shared.


Argument: Hard to convert exceptions to error codes at library boundry.
MM Counter: Example using dispatch()
Jan Counter: Not especially fast
MM Counter: Just use C++, it's flexible enough
Jan Counter: Get a clue
MM Counter: Show me that I need to get one.

Share this post


Link to post
Share on other sites

This topic is 4588 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this