Quote:Original post by Jan Wassenberg
Quote:The first article alone, including comments, is 32 pages long on my 1600x1200 monitor with fairly small text sizes. Reading the whole thing would entail reading every little off topic veer the commentators seem to like making, and every repeat of what's allready been said. Could you try pointing out some of the posts that underscore your point prehaps?
Ooh, that long? I read it during downtime, went fairly quickly. I've summarized the points referred to.
Quote:Lies, lies and deceptions my friend :-) (No, I'm not accusing you of lying, I just like using that phrase - but you are wrong). For a few examples against this, I give you:
My my, by now you are bending backwards pretty far. I wouldn't call that RAII anymore; closer to ScopeGuard (do something, report as finished or automatically clean up at exit). Oh BTW, raii_scope object didn't support that, did it? See how this is ballooning?
Ehm, no, I don't see. Implementing that extra functionality takes very few extra lines of code. As in, you add this member function:
void release( void ) { if ( cleaner ) { cleaner( value ); cleaner = 0; }}
Which is 6 lines, including single-character-liners. Hardly ballooning into a massive pile of high mantinence code. RAII simply makes sure things get destroyed, rather than forcing a certain order.
Quote:Quote:There's no way you can directly return an error code. It's valid C++, although considered bad practice by many, to throw during a destructor*.
Bad practice is mildly put. And the contortions you are going to to rescue the 'return error from dtor' thing are just amazing.
Only in the interests of making it completely generic ;-). For me, I prefer to have such things asserted() rather than exception throwing. If a handle is getting repeatedly released, the program is probably OK (or has allready done all the damage it can) and thus throwing an exception serves no purpouse - exceptions are meant to deal with exceptional circumstances, not bugs.
Quote:Quote:Quote:Well, no. With RAII built in, you add the complexity and ugliness of registering cleanup functions. No more is the utopic code that didn't appear to have any error handling code at all.
And how is this different from the ugliness of manually cleaning up yourself?
Congratulations, I am exasperated. I myself am saying that error code-style code is visible, while you are claiming "prettiness". Therefore, interesting to see you saying their ugliness is comparable :)
Error code style is both ugly and prone to uncaught errors. Exception code style is either ugly OR places the code elsewhere in the name of prettyness IF directly overlay non-raii code - directly overlaying raii over non-raii code is going to have the ugly associated with the non-raii code. Another possible usage form for raii_scope would be:
raii_scope< HANDLE , & CloseHandle > h( initial_value );
This has the added benifit of allowing one to create a typedef:
typedef raii_scope< HANDLE , & CloseHandle > raii_handle;
raii_handle h ( OpenFile( ... ) );
This means you have a one-liner somewhere that you're going to have to remember exists. Of course, you'd have to remember that there's a one liner that defines HANDLE out there somewhere in order to use the non RAII function, so, I call it pretty comparable in terms of usage :-);.
Quote:Quote:Quote:Looks ok. I have no idea though why you insist on throwing in the failure cases where you could just return an error code.
For consistancy. It's the choice between consitantly using error codes or consitantly using exceptions.
Oh saints preserve. Consistency is good, but you violate the very meaning of the word "exceptions" by treating all it-did-not-succeed conditions as such.
Unless you don't program everything to expect to fail, by making your code expect to succeed :-).
Quote:And let me tell you: once you actually understand how exceptions are implemented (starting with compiler tracking current objects, up to kernel SEH), you will probably have an entirely different opinion of what it means to throw one.
I've implemented exceptions in a toy compiler. I hope this qualifies me for the glorious title of knowing one of the many ways that exceptions can be implemented :-).
Quote:Quote:<sarcasm>Because normal usage of a checksum checking function is using it on a non existant file</sarcasm>
You'd be surprised. In my current work (100 KLOC, 30 hands on deck), it is commonplace for maps etc. to refer to not-yet-created objects.
And then you ask what the name or size of the object is, right?
No?
Refering to something that has yet to exist is one thing. Trying to use something that has yet to exist is another.
Quote:If you then crap out completely, you are wasting everyone's time until the issue is fixed (there is some question whether this approach or break-immediately is better, but that's beyond our scope here).
So don't completely crap out. To throw an exception means "There was something I couldn't deal with, prehaps you can do better". Want to report the double freeing of a handle? Don't throw an exception, open a message dialog and do it! Exceptions arn't the only way to deal with errors, but they do the same job and more than error codes.
Quote:I'd say anything that refers to IO must be treated as likely to fail; therefore, exceptions are by definition not warranted unless it's somehow more convenient there.
Likely to fail at some point yes (couldn't open the file, tried to read past the end of the file, etc), but not likely to fail on any given single read of a byte.
Quote:The try-it-first version (VerifyChecksumIfFileExists) would work, but now we are actually running into performance problems: seeking and opening files is *slow*. Therefore, there is no way around a try-it-and-report-status version, giving the caller the freedom to decide what to do on error.
industry:/home/panda# ls -R -l / > all_files 2>/dev/null... [3 seconds pass]panda@industry:~$ wc -l all_files118040 all_filespanda@industry:~$ _
On my crappy ass 600mhz old server built from the leftover parts of a Compaq, with it's crappy hard drive, it takes 3 seconds to list the permissions, owning group and user, file size, last modified date and time, and filename of each file on my computer. Let's make the extremely conservative estimate that there's only about 50 thousand files on my computer (based off the fact that all_files contains 118 thousand lines) and ignore the fact that the computer also has to format all this information and write it to a file.
By this calculation, my computer can find the sizes of over 16 thousand files in 1 second - or in other words, it takes 16-thousandths of a second to find the size of one file. I hardly call this slow :-).
Quote:Quote:Right, because WinAmp needs to know wheither or not my computer is on fire.
Indeed. (oh goody, sarcasm as a hedge bet, eh? ;p) When you get a bus error (memory defect), I'd want WinAmp to close gracefully and save my carefully constructed playlist, instead of having the OS nuke everything.
I'd rather the OS nuke everything, and not take the chance of possibly overwriting the playlist I've allready saved (since I've used windows, and word since before it gained the auto-save feature, enough to know to do this every so often or when finished creating something) with invalid jibberish.
Quote:Now picture this: the error happens while loading an mp3, and some idiot wrapped that in a catch(...), thus preventing the SIGBUS or whatnot from reaching the top-level handler. The error is ignored, a later error probably takes down the system, my data is lost and the coder should be run of of town on a rail.
I doubt that'd happen, people are so used to programs crashing and loosing all their data due to bugs that one realizes that one should save every now and then - programs such as Word now have features such as auto-save that help deal with problems that mean the system should shut the heck down.
Quote:Quote:Quote:Not when you have to check every new block of memory you allocate to make sure you actually got it.
Disagree. Transactional operation is easier when you cannot be interrupted at any time.
*snort* You're making this out to be a problem? Disagree.
But what does it have to do with transactional programming? Just how often do you hammer your heap, anyway? (side note: thanks to my nemesis Xerces, the game does about 200k allocs during startup. When running BoundsChecker or similar, I spend 30 minutes cursing them).
Sorry, I should've been more generalized. The statement about "check every new block" should be "check every function, every tiny little operation, even every new block or variable declaration."
My point is that if an exception can throw during the middle of a transaction, it's because you have an error occured. Similarly, the same applies if any of the functions return an error code.
A buggy "transactional" function without exceptions might look like:
void transfer( int ammount , const std::string & source_name , const std::string & destination_name ){ account & source = lookup_account( source_name ); source.cash -= ammount; account & destination = lookup_account( destination_name ); destination.cash += ammount;}
The code fails when we find out that the destination account dosn't exist. Unless we review the code and spot the bug, the only way to catch this is by doing error checking every step of the way, and even then, the error will only be caught at run time, once things are allready partly done - which means I consider this at best a stopgap measure. Now, if lookup_account dosn't return an error code, but throws instead, how could we deal with this?
Step 1: Add a throw() specification. Example:
struct transfer_failed : public std::exception { transfer_failed( void ) throw() {} ~transfer_failed( void ) throw() {} const char * what( void ) const throw() { return "Transfer failure" ; }};void example_transfer( int transfer , const std::string & source_name , const std::string & destination_name ) throw( transfer_failed )
Key point: this should be unique to the function (or possibly a set of overloaded functions), although it can inherit from a more generalized version.
Step 2:
Get a compiler error: lookup_account can throw other stuff!!!
Step 3: We now know there's a bug. We have a supposedly transactional function that isn't transactional. We fix it:
void transfer( int ammount , const std::string & source_name , const std::string & destination_name ) throw( transfer_failed ){ account * source; account * destination; try { //Do stuff that dosn't actually modify the values: source = & lookup_account( source_name ); destination = & lookup_account( destination_name ); } catch( account_does_not_exist ) { throw transfer_failed(); } source->cash -= ammount ; destination->cash += ammount ;}
Going by this methodology, I'd actually argue that transactional programming is EASIER with exceptions. If you want to be even safer, use a no-throw guarantee like this:
void do_actual_transfer( int ammount , account & source , account & destination ) throw(){ source.cash -= ammount ; destination.cash += ammount ;}void transfer( int ammount , const std::string & source_name , const std::string & destination_name ){ account & source = lookup_account( source_name ); account & destination = lookup_account( destination_name ); do_actual_transfer( ammount , source , destination );}
Quote:Quote:So far, I've seen these arguments on your part against RAII:
Correct, not sure if that's exhaustive. But ya know what? This really has been done to death (cf. another thread). If you want to use exceptions everywhere, fine. My comments on their pitfalls and when it makes sense to use error codes are apparently spoken into the wind. I only pray I don't have to use your APIs ;p
I feel exactly the same with my comments, and hold the same sentiments towards your APIs, I assure you ^_^.
Edit: Finished a sentance, added missing
opening tags.
[Edited by - MaulingMonkey on May 18, 2005 2:07:15 AM]