* try, finally @ 2008-03-19 16:13 Jason Cipriani 2008-03-19 16:14 ` Jason Cipriani ` (4 more replies) 0 siblings, 5 replies; 23+ messages in thread From: Jason Cipriani @ 2008-03-19 16:13 UTC (permalink / raw) To: gcc-help Does GCC have anything similar to the MS and Borland compiler's __try and __finally keywords? When using GCC I often find that I have code like this (a moderately complex, and highly contrived, example): ==== void *data1 = NULL, *data2 = NULL, *data3 = NULL; try { if (!(data1 = malloc(1000))) throw Something(); if (!(data2 = malloc(1000))) throw Something(); if (!(data3 = malloc(1000))) throw Something(); } catch (...) { // cleanup code free(data3); free(data2); free(data1); throw; } // the same cleanup code free(data3); free(data2); free(data1); === Where I am duplicating cleanup code for normal returns and exception handling, but what I really want to do is something like this: === void *data1 = NULL, *data2 = NULL, *data3 = NULL; __try { if (!(data1 = malloc(1000))) throw Something(); if (!(data2 = malloc(1000))) throw Something(); if (!(data3 = malloc(1000))) throw Something(); // do stuff } __finally { // cleanup code free(data3); free(data2); free(data1); } === Thanks, Jason ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 16:13 try, finally Jason Cipriani @ 2008-03-19 16:14 ` Jason Cipriani 2008-03-19 17:02 ` Tim Prince 2008-03-19 16:37 ` Brian Dessent ` (3 subsequent siblings) 4 siblings, 1 reply; 23+ messages in thread From: Jason Cipriani @ 2008-03-19 16:14 UTC (permalink / raw) To: gcc-help On Wed, Mar 19, 2008 at 12:13 PM, Jason Cipriani <jason.cipriani@gmail.com> wrote: > Does GCC have anything similar to the MS and Borland compiler's __try > and __finally keywords? When using GCC I often find that I have code > like this (a moderately complex, and highly contrived, example): Heh, well, the example wasn't really that complex. I had typed a weirder one and then simplified it but left this comment in. > > ==== > > void *data1 = NULL, *data2 = NULL, *data3 = NULL; > > try { > > if (!(data1 = malloc(1000))) > throw Something(); > if (!(data2 = malloc(1000))) > throw Something(); > if (!(data3 = malloc(1000))) > throw Something(); > > } catch (...) { > > // cleanup code > free(data3); > free(data2); > free(data1); > throw; > > } > > // the same cleanup code > free(data3); > free(data2); > free(data1); > > === > > Where I am duplicating cleanup code for normal returns and exception > handling, but what I really want to do is something like this: > > === > > void *data1 = NULL, *data2 = NULL, *data3 = NULL; > > __try { > > if (!(data1 = malloc(1000))) > throw Something(); > if (!(data2 = malloc(1000))) > throw Something(); > if (!(data3 = malloc(1000))) > throw Something(); > > // do stuff > > } __finally { > > // cleanup code > free(data3); > free(data2); > free(data1); > > } > > === > > Thanks, > Jason > ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 16:14 ` Jason Cipriani @ 2008-03-19 17:02 ` Tim Prince 0 siblings, 0 replies; 23+ messages in thread From: Tim Prince @ 2008-03-19 17:02 UTC (permalink / raw) To: Jason Cipriani; +Cc: gcc-help Jason Cipriani wrote: > On Wed, Mar 19, 2008 at 12:13 PM, Jason Cipriani > <jason.cipriani@gmail.com> wrote: >> Does GCC have anything similar to the MS and Borland compiler's __try >> and __finally keywords? When using GCC I often find that I have code >> like this (a moderately complex, and highly contrived, example): > > Heh, well, the example wasn't really that complex. I had typed a > weirder one and then simplified it but left this comment in. > The w32api headers in cygwin replicate some of this functionality. ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 16:13 try, finally Jason Cipriani 2008-03-19 16:14 ` Jason Cipriani @ 2008-03-19 16:37 ` Brian Dessent 2008-03-19 17:31 ` Ted Byers ` (2 subsequent siblings) 4 siblings, 0 replies; 23+ messages in thread From: Brian Dessent @ 2008-03-19 16:37 UTC (permalink / raw) To: Jason Cipriani; +Cc: gcc-help Jason Cipriani wrote: > Does GCC have anything similar to the MS and Borland compiler's __try > and __finally keywords? When using GCC I often find that I have code Adding SEH support to gcc has been tossed around for years but nothing usable has yet to come of it. You can still use SEH through SetUnhandledExceptionFilter() or by manipulating the exception chain at %fs:0 manually, but there is no built in compiler support for it. Brian ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 16:13 try, finally Jason Cipriani 2008-03-19 16:14 ` Jason Cipriani 2008-03-19 16:37 ` Brian Dessent @ 2008-03-19 17:31 ` Ted Byers 2008-03-19 17:37 ` me22 2008-03-25 22:28 ` Ian Lance Taylor 4 siblings, 0 replies; 23+ messages in thread From: Ted Byers @ 2008-03-19 17:31 UTC (permalink / raw) To: Jason Cipriani, gcc-help Looks like a routine C++ question. Unless I missed something, the RAII idiom applies. Replace your naked pointers by a class (an instance of a template such as an std::auto_ptr would suffice; or the boost shared_ptr is a good alternative should the semantics of how you're using the object require it). You create an instance of your object holding the resource and you are guaranteed it will be cleaned up regardless of what exit path you follow. If an exception occurs, the instance of the class will be cleaned up as part of how the stack is unwound. If you exit normally, it will be cleaned up once it goes out of scope. No need for syntactic sugar like __try/__finally. I don't know about what others prefer to do, or common practice if there is such a thing, but to me what passes for common sense is that avoiding vendor extensions whenever possible minimizes the pain experienced when the code must pass through another suite of development tools. And I like to pass my code through two different tool chains, just to catch potential issues that one is better at catching than the other. In other words, if it ain't in the standard, I don't use it unless there is really no other choice. In the 15 years I have been using C++, I have never found it necessary to use anything not in the standard (since it first apeared), and as a result I generally have no problem compiling it with any of the tools I regularly use (GCC and MSVC). What you say suggests you ought to take an hour or two to study RAII (resource acquisition is initialization), and see how far that allows you to clean up obviously problematic code. HTH Ted ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 16:13 try, finally Jason Cipriani ` (2 preceding siblings ...) 2008-03-19 17:31 ` Ted Byers @ 2008-03-19 17:37 ` me22 2008-03-19 19:09 ` Jason Cipriani 2008-03-25 22:28 ` Ian Lance Taylor 4 siblings, 1 reply; 23+ messages in thread From: me22 @ 2008-03-19 17:37 UTC (permalink / raw) To: Jason Cipriani; +Cc: gcc-help On Wed, Mar 19, 2008 at 12:13 PM, Jason Cipriani <jason.cipriani@gmail.com> wrote: > Does GCC have anything similar to the MS and Borland compiler's __try > and __finally keywords? When using GCC I often find that I have code > like this (a moderately complex, and highly contrived, example): > > ==== > > [snip code] > Basically the same thing in C++: If you wanted it as an array: #include <vector> std::vector<char> data1(1000), data2(1000), data3(1000); Or, if you're allocating for an object, #include <memory> std::auto_ptr<whatever> data1(new whatever); std::auto_ptr<whatever> data2(new whatever); std::auto_ptr<whatever> data3(new whatever); And that's it. C++'s (default) new throws on failure instead of returning 0, and the destructors make sure everything that's been constructed gets destructed properly. Why bother with void* and C? ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 17:37 ` me22 @ 2008-03-19 19:09 ` Jason Cipriani 2008-03-19 19:09 ` me22 2008-03-19 21:04 ` Ted Byers 0 siblings, 2 replies; 23+ messages in thread From: Jason Cipriani @ 2008-03-19 19:09 UTC (permalink / raw) To: me22; +Cc: gcc-help > On Wed, Mar 19, 2008 at 12:13 PM, Jason Cipriani > <jason.cipriani@gmail.com> wrote: > > > Does GCC have anything similar to the MS and Borland compiler's __try > > and __finally keywords? When using GCC I often find that I have code > > like this (a moderately complex, and highly contrived, example): Thanks for your responses, guys. On Wed, Mar 19, 2008 at 12:37 PM, Brian Dessent <brian@dessent.net> wrote: > Adding SEH support to gcc has been tossed around for years but nothing > usable has yet to come of it. You can still use SEH through > SetUnhandledExceptionFilter() or by manipulating the exception chain at > %fs:0 manually, but there is no built in compiler support for it. Oh well. Maybe some day. Thanks for the tips. I probably won't do anything like messing with SetUnhandledExceptionFilter(), mostly I just want to simplify code in common situations, I don't want to do anything *too* strange, though. On Wed, Mar 19, 2008 at 1:02 PM, Tim Prince <tprince@myrealbox.com> wrote: > The w32api headers in cygwin replicate some of this functionality. Thanks. I'll check those out just to see how they do it, but I'm actually doing Linux development at the moment, and on Windows I usually use MinGW GCC. On Wed, Mar 19, 2008 at 1:30 PM, Ted Byers <r.ted.byers@rogers.com> wrote: > What you say suggests you ought to take an hour or two > to study RAII (resource acquisition is > initialization), and see how far that allows you to > clean up obviously problematic code. Agreed; I know the idiom but I've been always avoided the std and boost RAII utilities for some reason -- however, it's 2008 and probably about time for me to bite the bullet, especially with C++0x coming out some day, I have no excuse. One situation I frequently find myself in is something like: "Ugh... I want to write this code and I really don't feel like implementing everything required to make resource cleanup automatic here; I'll just put cleanup code everywhere instead". In reality, those things are already written, I guess. me22 <me22.ca@gmail.com> wrote: > Why bother with void* and C? Just an example. So, WRT what Ted Byers and me22 said, an issue I typically have that I always assumed the std / boost utilities couldn't handle (and hence one reason why I never bother looking into them), is that it's not always as simple as creating objects with new and delete. For example, some code I recently wrote (and what prompted me to ask this question) used libxml2, a C library, from my C++ code; say something like this (if you are actually familiar with libxml2; I made up the FindChildNode function): ===== xmlDoc *doc = NULL; xmlNode *root, *node; xmlChar *str1 = NULL, *str2 = NULL, *str3 = NULL; // load document, fail on error if (!(doc = xmlReadFile(...))) throw Something(); try { // get root xml node, fail if document is empty if (!(root = xmlDocGetRootElement(doc))) throw Something(); // get "first" node string into str1, fail on error if (!(node = FindChildNode(root, "first"))) throw Something(); if (!(str1 = xmlNodeGetContent(node))) throw Something(); // get "second" node string into str2, fail on error if (!(node = FindChildNode(root, "second"))) throw Something(); if (!(str2 = xmlNodeGetContent(node))) throw Something(); // get "third" node string into str3, fail on error if (!(node = FindChildNode(root, "third"))) throw Something(); if (!(str3 = xmlNodeGetContent(node))) throw Something(); // do something with str1, str2, str3 } catch (...) { xmlFree(str3); xmlFree(str2); xmlFree(str1); xmlFreeDoc(doc); xmlCleanupParser(); throw; } // and duplicate cleanup code: xmlFree(str3); xmlFree(str2); xmlFree(str1); xmlFreeDoc(doc); xmlCleanupParser(); ===== Now, in this specific example: there are a few other third-party libraries available that provide C++ bindings to libxml2 -- let's say that for whatever reason they are not acceptable here. And in the general case, the cleanup code here is not "deleting" something, they are custom cleanup functions that I have no control over. If my goal is to reduce the amount of coding I have to do, it is far easier for me to duplicate the cleanup code than to go and write a bunch of small C++ wrapper objects with automatic cleanup that I can use interchangeably with the libxml2 data types. Does std or boost have some kind of "smart pointers" that let me define the cleanup actions without having to write too much additional code? Thanks, Jason ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 19:09 ` Jason Cipriani @ 2008-03-19 19:09 ` me22 2008-03-20 5:50 ` Jason Cipriani 2008-03-20 13:30 ` Noel Yap 2008-03-19 21:04 ` Ted Byers 1 sibling, 2 replies; 23+ messages in thread From: me22 @ 2008-03-19 19:09 UTC (permalink / raw) To: Jason Cipriani; +Cc: gcc-help On Wed, Mar 19, 2008 at 3:02 PM, Jason Cipriani <jason.cipriani@gmail.com> wrote: > Does std or boost have some kind of "smart pointers" that let me > define the cleanup actions without having to write too much additional > code? > Yup, boost::shared_ptr (std::shared_ptr, in C++1x) will let you do that. boost::shared_ptr<xmlDoc> doc( xmlReadFile(...), xmlFreeDoc ); if (!doc) throw something(); etc ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 19:09 ` me22 @ 2008-03-20 5:50 ` Jason Cipriani 2008-03-20 13:30 ` Noel Yap 1 sibling, 0 replies; 23+ messages in thread From: Jason Cipriani @ 2008-03-20 5:50 UTC (permalink / raw) To: me22; +Cc: gcc-help On Wed, Mar 19, 2008 at 3:09 PM, me22 <me22.ca@gmail.com> wrote: > On Wed, Mar 19, 2008 at 3:02 PM, Jason Cipriani > > <jason.cipriani@gmail.com> wrote: > > > Does std or boost have some kind of "smart pointers" that let me > > define the cleanup actions without having to write too much additional > > code? > > > > Yup, boost::shared_ptr (std::shared_ptr, in C++1x) will let you do that. > > boost::shared_ptr<xmlDoc> doc( xmlReadFile(...), xmlFreeDoc ); > if (!doc) throw something(); Thanks, this is exactly the kind of solution I was looking for. Jason ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 19:09 ` me22 2008-03-20 5:50 ` Jason Cipriani @ 2008-03-20 13:30 ` Noel Yap 2008-03-21 2:27 ` Jason Cipriani 1 sibling, 1 reply; 23+ messages in thread From: Noel Yap @ 2008-03-20 13:30 UTC (permalink / raw) To: me22; +Cc: Jason Cipriani, gcc-help On Wed, Mar 19, 2008 at 12:09 PM, me22 <me22.ca@gmail.com> wrote: > On Wed, Mar 19, 2008 at 3:02 PM, Jason Cipriani > > <jason.cipriani@gmail.com> wrote: > > > Does std or boost have some kind of "smart pointers" that let me > > define the cleanup actions without having to write too much additional > > code? > > > > Yup, boost::shared_ptr (std::shared_ptr, in C++1x) will let you do that. > > boost::shared_ptr<xmlDoc> doc( xmlReadFile(...), xmlFreeDoc ); > if (!doc) throw something(); If the pointers never leave the scope, I would use boost::scoped_ptr. Since shared_ptr keeps a reference count, locks (or atomic operations) need to be used in order to make it thread safe. Use shared_ptr iff the ownership needs to be shared. Use auto_ptr iff the ownership needs to be transferred and the pointer must never be copied. Use scoped_ptr iff the life span of the memory ought to end when the local scope ends. Noel ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-20 13:30 ` Noel Yap @ 2008-03-21 2:27 ` Jason Cipriani 0 siblings, 0 replies; 23+ messages in thread From: Jason Cipriani @ 2008-03-21 2:27 UTC (permalink / raw) To: Noel Yap; +Cc: me22, gcc-help On Thu, Mar 20, 2008 at 9:30 AM, Noel Yap <noel.yap@gmail.com> wrote: > > On Wed, Mar 19, 2008 at 12:09 PM, me22 <me22.ca@gmail.com> wrote: > > On Wed, Mar 19, 2008 at 3:02 PM, Jason Cipriani > > > > <jason.cipriani@gmail.com> wrote: > > > > > Does std or boost have some kind of "smart pointers" that let me > > > define the cleanup actions without having to write too much additional > > > code? > > > > > > > Yup, boost::shared_ptr (std::shared_ptr, in C++1x) will let you do that. > > > > boost::shared_ptr<xmlDoc> doc( xmlReadFile(...), xmlFreeDoc ); > > if (!doc) throw something(); > > If the pointers never leave the scope, I would use boost::scoped_ptr. > Since shared_ptr keeps a reference count, locks (or atomic operations) > need to be used in order to make it thread safe. > > Use shared_ptr iff the ownership needs to be shared. Use auto_ptr iff > the ownership needs to be transferred and the pointer must never be > copied. Use scoped_ptr iff the life span of the memory ought to end > when the local scope ends. Thanks for the tips! Jason > > Noel > ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 19:09 ` Jason Cipriani 2008-03-19 19:09 ` me22 @ 2008-03-19 21:04 ` Ted Byers 2008-03-20 6:28 ` Jason Cipriani 1 sibling, 1 reply; 23+ messages in thread From: Ted Byers @ 2008-03-19 21:04 UTC (permalink / raw) To: Jason Cipriani; +Cc: gcc-help --- Jason Cipriani <jason.cipriani@gmail.com> wrote: > > On Wed, Mar 19, 2008 at 12:13 PM, Jason Cipriani > > <jason.cipriani@gmail.com> wrote: > > > > > Does GCC have anything similar to the MS and > Borland compiler's __try > > > and __finally keywords? When using GCC I often > find that I have code > > > like this (a moderately complex, and highly > contrived, example): > > Thanks for your responses, guys. > > On Wed, Mar 19, 2008 at 12:37 PM, Brian Dessent > <brian@dessent.net> wrote: > > Adding SEH support to gcc has been tossed around > for years but nothing > > usable has yet to come of it. You can still use > SEH through > > SetUnhandledExceptionFilter() or by manipulating > the exception chain at > > %fs:0 manually, but there is no built in compiler > support for it. > > Oh well. Maybe some day. Thanks for the tips. I > probably won't do > anything like messing with > SetUnhandledExceptionFilter(), mostly I > just want to simplify code in common situations, I > don't want to do > anything *too* strange, though. > But examining it for yourself, and possibly developing exception classes as wrappers for SEH would be a useful exercise anyway, and if well done may provide for more useful and more robust exception handling. > [snip] > On Wed, Mar 19, 2008 at 1:30 PM, Ted Byers > <r.ted.byers@rogers.com> wrote: > > What you say suggests you ought to take an hour > or two > > to study RAII (resource acquisition is > > initialization), and see how far that allows you > to > > clean up obviously problematic code. > > Agreed; I know the idiom but I've been always > avoided the std and > boost RAII utilities for some reason -- however, > it's 2008 and > probably about time for me to bite the bullet, > especially with C++0x > coming out some day, I have no excuse. > > One situation I frequently find myself in is > something like: "Ugh... I > want to write this code and I really don't feel like > implementing > everything required to make resource cleanup > automatic here; I'll just > put cleanup code everywhere instead". In reality, > those things are > already written, I guess. > This suggests you don't really understand RAII. There are no std or boost "utilities" specific to RAII. rather, there are classes in both the standard C++ library and in boost that are useful in making implementing RAII trivially easy. If you are not passing your pointers around among a suite of functions, then using RAII can be as simple as using an std::auto_ptr instead of a naked pointer. If, however, you are passing your pointers around a suite of functions, then the boost::shared_ptr is a smart choice. Just replace the naked pointer by a smart pointer. To ignore such a common idiom dooms you to reinventing the wheel, ignoring the accumulated experience of software developers with more, or different, experience than you have yourself. Even though I have been programming for almost 30 years, I am ALWAYS looking for emerging idioms and new design patterns that better programmers than I have discovered! And when I find one that is new to me, and that appears relevant to a problem I am working on, I use it. I am not too proud to learn from others. > me22 <me22.ca@gmail.com> wrote: > > Why bother with void* and C? > > Just an example. > > So, WRT what Ted Byers and me22 said, an issue I > typically have that I > always assumed the std / boost utilities couldn't > handle (and hence > one reason why I never bother looking into them), is > that it's not > always as simple as creating objects with new and > delete. Get your classes right, with whatever inheritance and containment necessary, and it CAN become as simple as creating objects with new, and destructing them with delete. (Actually, as far as possible, you want to leave invoking delete to your smart pointers!!!) > For example, > some code I recently wrote (and what prompted me to > ask this question) > used libxml2, a C library, from my C++ code; say > something like this > (if you are actually familiar with libxml2; I made > up the > FindChildNode function): > > ===== > > xmlDoc *doc = NULL; > xmlNode *root, *node; > xmlChar *str1 = NULL, *str2 = NULL, *str3 = NULL; > Just replacing ALL these pointers with smart pointers would be a decent first cut at making it exception safe. The following code strikes me as an abuse of exception handling. Exceptions ought to be reserved for unpredictable, rare error conditions. Unless you are doing ONLY batch processing, you want your interface to be as user friendly as possible. Therefore, you want to, and can, know precisely what conditions would result in your call to, say, xmlReadFile failing, and can test for it. If your test(s) for such conditions that could lead to failure identifies a potential problem, you can notify your user of the problem and prompt him to take corrective action. Your error condition, then, doesn't arise. User error WRT data input is common enough that it is much better to test for all errors that you can test for, and prompt the user for corrective action than it is to just throw exceptions willy nilly every time a statement doesn't give you what you wanted. To use a trivial example, suppose your code does mathematical analysis and you routinely evaluate a quotient "x / (a - b)". You KNOW going in that you'll encounter a divide by zero error condition either if a = b or both 'a' and 'b' = 0. Instead of just letting the error happen, you test for the possibility of the error condition, and when detected you terminate the affected call, returning control to a function that prompts the user for corrective action and handle the result. If a programmer working for me showed me code like that which you show here, I would probably send him back to the drawing board for there is no evidence of any analysis of error conditions and possible error prevention. Such a failure guarantees a less than adequate user experience, and significant wasted user time, squandered on trying to figure out why the program didn't do what the user expected it to do. > // load document, fail on error > if (!(doc = xmlReadFile(...))) > throw Something(); > > try { > > // get root xml node, fail if document is empty > if (!(root = xmlDocGetRootElement(doc))) > throw Something(); > > // get "first" node string into str1, fail on > error > if (!(node = FindChildNode(root, "first"))) > throw Something(); > if (!(str1 = xmlNodeGetContent(node))) > throw Something(); > > // get "second" node string into str2, fail on > error > if (!(node = FindChildNode(root, "second"))) > throw Something(); > if (!(str2 = xmlNodeGetContent(node))) > throw Something(); > > // get "third" node string into str3, fail on > error > if (!(node = FindChildNode(root, "third"))) > throw Something(); > if (!(str3 = xmlNodeGetContent(node))) > throw Something(); > > // do something with str1, str2, str3 > > } catch (...) { > > xmlFree(str3); > xmlFree(str2); > xmlFree(str1); > xmlFreeDoc(doc); > xmlCleanupParser(); > throw; > > } > > // and duplicate cleanup code: > xmlFree(str3); > xmlFree(str2); > xmlFree(str1); > xmlFreeDoc(doc); > xmlCleanupParser(); > > ===== > > Now, in this specific example: there are a few other > third-party > libraries available that provide C++ bindings to > libxml2 -- let's say > that for whatever reason they are not acceptable > here. And in the > general case, the cleanup code here is not > "deleting" something, they > are custom cleanup functions that I have no control > over. > > If my goal is to reduce the amount of coding I have > to do, it is far > easier for me to duplicate the cleanup code than to > go and write a > bunch of small C++ wrapper objects with automatic > cleanup that I can > use interchangeably with the libxml2 data types. > This doesn't make sense. If you can write cleanup code, regardless of which library you're using, you can do so efficiently using RAII. You can't use the existance of one badly written library or another as a rational for poorly structuring your own code. If for some reason, you don't like existing wrapper libraries for libxml2, then roll your own. Trust me, your preference for copy and paste duplication of your "cleanup" code will come back to bite you at a most inconvenient moment. Your code will bloat as you develop it further, eventually becoming a maintenance nightmare. Trust me, I have worked on projects that, efficiently coded, had in excess of half a million lines of code, and when a project gets that big, the last thing you want to do is refactor to remove code duplication. It is much better to get it right the first time through that to have to fix it after it has become a bloated monster. > Does std or boost have some kind of "smart pointers" > that let me > define the cleanup actions without having to write > too much additional > code? > Yes, both do. std::auto_ptr and boost::shared_ptr, and there are variants of each used for pointers to arrays. You do have to make sure you use the right version of each, for each pointer you're replacing. But if you have to use functions like 'xmlFree' to properly clean up, there is no option other than to write your own class that invokes such functions in its destructor. The smart pointers only invoke delete on the enclosed pointer at the right time. Even so, writing a wrapper class for a resource you have to manage remains trivially easy, and much more maintainable than your copy and paste approach. And if you must keep your resource on the heap rather than on the stack, you would still use your custom resource class WITH one of the smart pointers. HTH Ted ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 21:04 ` Ted Byers @ 2008-03-20 6:28 ` Jason Cipriani 2008-03-20 12:24 ` John Love-Jensen 2008-03-20 14:50 ` Ted Byers 0 siblings, 2 replies; 23+ messages in thread From: Jason Cipriani @ 2008-03-20 6:28 UTC (permalink / raw) To: Ted Byers; +Cc: gcc-help Thanks for the long response, Ted. Although I disagree with most of it, I appreciate your time. Some of the points you have made are slightly unclear: On Wed, Mar 19, 2008 at 5:04 PM, Ted Byers <r.ted.byers@rogers.com> wrote: > The following code strikes me as an abuse of exception > handling. Exceptions ought to be reserved for > unpredictable, rare error conditions. How would you handle predictable, common error conditions, then? And what do you consider to be unpredictable and rare? Going back to the XML example (since it's an easy one to build on), let's say I have a document structured like this: <root> <something> <value> <data> </data> </value> </something> </root> If I was writing code to parse that, and found "root", then "something", then "value", but not "data", I would call that a rare error. Also the only way to really recover from it is to let the user know what happened, and to leave the program in a valid state or terminate it. Using exceptions that way provides for both in a very simple way. In my examples, for simplicity, I threw a Something() -- but associating error messages with exceptions, and a good exception inheritance structure, makes error handling and reporting very easy (e.g. throw EDocumentFormatError(__FILE__, __LINE__, "Data node is missing from file.")). Take Java as an extreme example of the use of exceptions. > Unless you are > doing ONLY batch processing, you want your interface > to be as user friendly as possible. Therefore, you > want to, and can, know precisely what conditions would > result in your call to, say, xmlReadFile failing, and > can test for it. If your test(s) for such conditions > that could lead to failure identifies a potential > problem, you can notify your user of the problem and > prompt him to take corrective action. By this I assume you mean ask libxml for a clear error string and somehow display it to the user, correct? If that is the case, I agree. I hate receiving error messages such as "Error: Operation failed", and so I do what I can to not display vague messages like that to the user either. I did leave all that out of my example. However, including the error message in an exception is an extremely clean way of passing that information back to the caller. What would your alternative be? Returning error codes from functions becomes messy, and setting global error strings with functions such as GetLastErrorMessage() (or whatever, you get the idea) comes with its own set of problems. > Your error > condition, then, doesn't arise. I am not sure what you mean. If a file does not exist and therefore can not be opened, notifying the user of the error does not cause the file to exist. > User error WRT data > input is common enough that it is much better to test > for all errors that you can test for, and prompt the > user for corrective action than it is to just throw > exceptions willy nilly every time a statement doesn't > give you what you wanted. Agreed. I don't see what is willy nilly. In this particular example, I have an XML file that contains some data that I need to load. The XML file must adhere to a very specific structure that has been defined elsewhere. When the user chooses to load the file, the file must either be 100% compliant with the defined structure, or it will fail to load. I have no desire to prompt the user with message boxes such as "Sorry, but the X data element was missing from this data file, would you mind telling me what you think the value should be so I can recover and continue?" A user would not be able to answer that question in my case anyway. Instead, if the file has a slight problem (it should not, as the files are not hand-generated), I want to notify the user that the file could not be loaded. Of course, I would provide them with a reason why the file could not be loaded, but it is not always necessary, or even possible, to allow the user to intervene and correct an error condition themselves. Therefore, in the XML example, if any one of those operations fails, the entire load operation *should* fail, and therefore every operation warrants an error return on failure. In this case, exceptions are a great error return, because they can store a lot more information than, say, returning an integer from a function. In fact, this is *precisely* what exceptions are designed for. > To use a trivial example, suppose your code does > mathematical analysis and you routinely evaluate a > quotient "x / (a - b)". You KNOW going in that you'll > encounter a divide by zero error condition either if a > = b or both 'a' and 'b' = 0. Instead of just letting > the error happen, you test for the possibility of the > error condition, and when detected you terminate the > affected call, ... I agree completely. This is very reasonable. > ...returning control to a function that > prompts the user for corrective action and handle the > result. This seems like a reasonable way to do that: class EDivideByZero : public Exception { ... }; int DoTheMath (int x, int a, int b) throw (EDivideByZero) { if (a == b) throw EDivideByZero("Divide by zero: A must not equal B"); else return x / (a - b); } void DoWhatever () { try { DoTheMath(4, 2, 4); } catch (Exception &e) { DisplayTheError(e.TheMessage); } } This notifies the user of the problem, and this also allows DoWhatever() to handle errors without ever having to have knowledge of what the error was. It is up to the source of the error to describe the error. > If a programmer working for me showed me code like > that which you show here, I would probably send him > back to the drawing board for there is no evidence of > any analysis of error conditions and possible error > prevention. I am not sure what you mean. There is analysis of error conditions -- all operations are checked for errors. Errors are not left unhandled. If by no "error prevention" you mean that the user is not given any notification of *what* operation caused the error -- well I assure you that's because I left out specific error messages in my example, since I was asking about SEH and they weren't relevant. > Such a failure guarantees a less than > adequate user experience, and significant wasted user > time, squandered on trying to figure out why the > program didn't do what the user expected it to do. Much of what you say is based on the fact that my example had no specific error messages. Given that, everything you say is completely reasonable. However, in the actual code, I do provide error messages, so most of the issues you are talking about are not problems. In fact, I even include the file name in the error messages. <g> > > If my goal is to reduce the amount of coding I have > > to do, it is far > > easier for me to duplicate the cleanup code than to > > go and write a > > bunch of small C++ wrapper objects with automatic > > cleanup that I can > > use interchangeably with the libxml2 data types. > > > This doesn't make sense. If you can write cleanup > code, regardless of which library you're using, you > can do so efficiently using RAII. You can't use the > existance of one badly written library or another as a > rational for poorly structuring your own code. I am not attempting to rationalize poor structuring of any code. > If for > some reason, you don't like existing wrapper libraries > for libxml2, then roll your own. Trust me, your > preference for copy and paste duplication of your > "cleanup" code will come back to bite you at a most > inconvenient moment. Your code will bloat as you > develop it further, eventually becoming a maintenance > nightmare. Yes, I know this. This is precisely why I came here to ask about SEH in GCC... SEH is a feature in other compilers that I use precisely to prevent the bloat. GCC did not provide it, and now I am looking for an alternative. You are preaching to the choir, my friend. > Trust me, I have worked on projects that, > efficiently coded, had in excess of half a million > lines of code, and when a project gets that big, the > last thing you want to do is refactor to remove code > duplication. It is much better to get it right the > first time through that to have to fix it after it has > become a bloated monster. Again you are preaching to the choir. I promise. :-) > Yes, both do. std::auto_ptr and boost::shared_ptr, > and there are variants of each used for pointers to > arrays. You do have to make sure you use the right > version of each, for each pointer you're replacing. > But if you have to use functions like 'xmlFree' to > properly clean up, there is no option other than to > write your own class that invokes such functions in > its destructor. Thanks. Actually, it seems that the boost::shared_ptr lets me use xmlFree without writing my own class to do it -- see me22's previous reply -- which is a perfect solution to my issue of duplicating cleanup code. > The smart pointers only invoke delete > on the enclosed pointer at the right time. They can do other things besides delete. > Even so, > writing a wrapper class for a resource you have to > manage remains trivially easy, and much more > maintainable than your copy and paste approach. Writing a wrapper class is not necessary as long as your cleanup function takes only one parameter (such as xmlFree), see boost::shared_ptr. However, it is definitely more maintainable than my copy and paste approach. > And > if you must keep your resource on the heap rather than > on the stack, you would still use your custom resource > class WITH one of the smart pointers. You can just use the smart pointers directly. > > HTH It is certainly food for thought. I do appreciate your reply. > > Ted Jason ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-20 6:28 ` Jason Cipriani @ 2008-03-20 12:24 ` John Love-Jensen 2008-03-21 2:11 ` Jason Cipriani 2008-03-20 14:50 ` Ted Byers 1 sibling, 1 reply; 23+ messages in thread From: John Love-Jensen @ 2008-03-20 12:24 UTC (permalink / raw) To: Jason Cipriani; +Cc: GCC-help, Ted Byers Hi Jason, > How would you handle predictable, common error conditions, then? In my projects, throwing an exception mean "the application is about to terminate." That's using the exception mechanism at the extreme of conservative programming with exceptions. Assuming you use exceptions with less draconian policy, the exception mechanism is not for using as normal flow control. It really means an exceptional situation, outside of the normal flow control. For normal flow control -- such as handling predictable, common error conditions -- you should use return codes. One way to have more "in your face" return codes, is to return "smart return code objects". A smart return code object has this kind of behavior: + wraps a return code + if the caller does read (query) the return code object, THEN the object marks itself as having been checked AND the object's destructor is silent + if the caller does not read (query) the return code object, AND the return code object does NOT have an error status, THEN the object's destructor is silent + if the caller does not read (query) the return code object, AND the return code object DOES have an error status, THEN the object's destructor does "something" Where "something" is one or more of... + an assert + a trace to a log + throw an exception I consider a "smart return code object" to be training wheels for result codes. But for larger teams, they become very helpful. > And what do you consider to be unpredictable and rare? Any violation of the routine's contractual requirements. For example, if an int parameter must have the value of 0, 1 or 2, and the value passed in is something other than 0, 1 or 2. Any violation of the routine's contractual obligations. For example, if the routine must put the object in state A or B, but was unable to do so and the object is in state C (a "never happen" situation). For example, if the object is in an inconsistent state upon return, such that the object's invariance is violated. This can happen if a state change cannot be completed as a transaction, and enough has happened such that the state of the object cannot be reverted. (That's why have transaction based assignment operator -- perhaps using the swap paradigm -- is so very useful.) Any "never happen" situations. (Where, in the case of my applications, keeping in mind that throwing an exception means "terminate the application, forthwith". So throw an exception where it is appropriate to terminate the application works as a rule-of-thumb in my application. Your exception usage policy will likely be less extreme.) Any "is it okay if the processing continues (without throwing or returning an error code) with the detected 'broken axle' condition?" If it's not okay, then either throw or return an error code. > If I was writing code to parse that, and found "root", then > "something", then "value", but not "data", I would call that a rare... I presume you are speaking of the "meta-layer" (the decision making layer) above the parser itself. In my application, ponder "is this failure such that the application should be terminated ... or can it be handled in some reasonable failsafe fallback and the application continue running?" Also useful is writing your own throw handler such that if a throw happens it forks the application and core dumps the child (useful to have "core.<PID>" files enabled). That's how rarely I expect an exception to occur, even when using exceptions with less draconian policies than "terminate the application, forthwith". And with a core you have a good snapshot of the application and where things went awry. Also, once you start using exceptions, you have to be careful too. Exceptions cannot be thrown through a C barrier. That means that you cannot propagate an exception through an OS callback or other C callbacks. You cannot propagate an exception outside of a thread's entry routine. And on some platforms, you cannot propagate an exception out of the "module" (DLL) that generated the exception. > Take Java as an extreme example of the use of exceptions. Java exceptions are not the same thing as C++ exceptions. In my opinion, Java exceptions are much more useful, and robust, and can be used in Java situations where one would not use C++ exceptions in analogous C++. > What would your alternative be? Returning error codes from functions. Even if it is "messy". > on failure. In this case, exceptions are a great error return, because > they can store a lot more information than, say, returning an integer > from a function. In fact, this is *precisely* what exceptions are > designed for. An error code from a function does not need to be an int. The error code can be an object that can store a lot more information. > Yes, I know this. This is precisely why I came here to ask about SEH > in GCC... SEH is a feature in other compilers that I use precisely to > prevent the bloat. GCC did not provide it, and now I am looking for an > alternative. You are preaching to the choir, my friend. C++ has RAII, which is just as useful, and is standard. No need for an SEH compiler extension. I'm glad you found Boost -- amazingly cool C++ enhancers. :-) Sincerely, --Eljay Note: Ted Byers is spot on. I would have answered in exactly the same way as he did, but I doubt I could have answered as eloquently. Note: Read Herb Sutter's Exceptional C++. Also, read Herb Sutter & Andrei Alexandrescu's C++ Coding Standards. ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-20 12:24 ` John Love-Jensen @ 2008-03-21 2:11 ` Jason Cipriani 2008-03-21 2:37 ` me22 ` (2 more replies) 0 siblings, 3 replies; 23+ messages in thread From: Jason Cipriani @ 2008-03-21 2:11 UTC (permalink / raw) To: John Love-Jensen; +Cc: GCC-help, Ted Byers On Thu, Mar 20, 2008 at 8:23 AM, John Love-Jensen <eljay@adobe.com> wrote: > In my projects, throwing an exception mean "the application is about to > terminate." > > That's using the exception mechanism at the extreme of conservative > programming with exceptions. > > Assuming you use exceptions with less draconian policy, the exception > mechanism is not for using as normal flow control. It really means an > exceptional situation, outside of the normal flow control. > > For normal flow control -- such as handling predictable, common error > conditions -- you should use return codes. It seems that the root of any disagreement is what kinds of errors we'd prefer to represent with exceptions. You and Ted would use them for rare, fatal error conditions, similar in spirit to machine exceptions such as access violations and invalid instructions. I would use them for more common errors such as invalid user input, missing files, network errors, etc. I have a hunch nobody is going to be having a change of heart any time soon. :-) One thing that I have often noticed, incidentally, is that the longer a programmer has been programming before C++, the more they prefer error codes to exceptions. One thing I can do, however, is present a strong example of benefits of using exceptions for more common errors rather than error return codes. I can think of many such examples but I'll try to construct one good one. I will warn you ahead of time, this will be a long email (think of it more as an article, and feel free to comment). :-) The first example is fairly simple. It becomes more pronounced in larger applications. Let's say you have a base class that defines an interface to something, and many derived classes that implement that. This is a well-known and common situation. Here the example can be a base interface that describes how to get, say, an image from some source (Image is an object that can hold an image). For simplicity's sake we'll say that derived constructors and destructors should not cause errors, and that all real work should be done by GetImage(): class CImageSource { public: CImageSource (); virtual ~CImageSource (); virtual void GetImage (Image &img) = 0; }; Let's say we have 30 different image sources, but here are two of them. First, using exceptions to represent error conditions: // This is the base exception we'll be using. It contains a lot of info. class EBaseException { // let these even have a function to display a message box: void GUINotify (...); // perhaps this takes some handle to current window, etc. // ... interfaces to private data left out of example ... private: string sourcefile_; int sourceline_; string message_; Severity severity_; // fatal? warning? etc. StackTrace trace_; // hypothetical stack trace for sending dump to author. // etc... }; // Provides an image from an HTTP resource. class CHTTPImageSource { public: void GetImage (Image &) throw (EBaseException) }; // Provides an image from the FOO-1200 high performance xray vision // camera, which has a custom set of drivers for talking to it. The // author of this code assumes no responsibility for misuse of this // device. ;-) class CFOO1200ImageSource { public: void GetImage (Image &) throw (EBaseException) }; You should be getting the idea at this point. Let's say that our program has a GUI on it. The user has configured the image source elsewhere. There is a button on the GUI that the user can press to grab an image from the image source. Pressing that button should display the image, notifying the user if an error occurs. Let's say pressing that button calls OnButton(), which calls GrabAndDisplayImage() to display the image, again using exceptions: // In this example ENullPointerException derives from EBaseException, and // display_ is some thing that displays images. I've left these out. I have also // used ... in the exception constructor to represent the other things you may // pass to the hypothetical constructor, __FILE__, __LINE__, whatever. void UI::GrabAndDisplayImage (CImageSource *source) throw (Exception) { Image img; if (!source) throw ENullPointerException(..., "No image source specified.", ...); source->GetImage(img); display_->DisplayImage(img); } void UI::OnButton () { try { GrabAndDisplayImage(source_); } catch (Exception &x) { x.GUINotify(); } } And that's it for the higher level part. The various implementations of CImageSource throw exceptions containing information about errors specific to those types of devices. Each CImageSource may even even define it's own EBaseException subclasses that it can use, the rest of the application need not be aware of these types. The use of 'image' in GrabAndDisplayImage() provides for automatic cleanup if any exceptions are thrown. The button on the GUI does what it needs to do: grabs and displays the image, notifying the user on error. It is assumed that the error messages are well-constructed enough to contain recovery instructions. The code is also exceptionally (pun intended) clean. There is no duplicated cleanup code, no translation of error codes from one form to another, no querying of error strings, no hard-to-read branching. On top of that, every error may have a wealth of other information associated with it -- information that you could send to the author as a bug report (functionality that could be taken care of by EBaseException::GUINotify()), information about the severity of the error. Logging can be done in the EBaseException constructor to provide automatic and complete error logging without modifying any application code. Everything just works. Also the possibilities are endless. You can take advantage of the exception hierarchy to provide specific recovery options: try { GrabAndDisplayImage(source_); } catch (EConfigurationError &x) { x.GUINotify(); DisplayConfigurationGUI(); } catch (EBaseException &b) { b.GUINotify(); } Here, for example, any exception thrown that has derived from EConfigurationError will cause the code to display a configuration settings dialog to the user. This provides you with a way to handle entire classes of errors in similar ways, without caring about the individual details, and on a per-function basis (for example, in a batch processing scenario you may just handle EBaseException and not treat EConfigurationError specially). I have even seen applications designed where exceptions know a little bit about how to recover from an error. For example: class EBaseException { public: virtual void Recover () { } }; class EConfigurationError : public EBaseException { public: virtual void Recover () { DisplayConfigurationGUI(); } }; And so on and so forth. That is the magic of exceptions. Now, let's say we do not want to use exceptions. Instead we are going to use error codes here. Everything that was done with exceptions above is also entirely possible using error codes. It's not a matter of what you can and can't do. However, a lot of extra coding will be required to emulate all the features of exceptions using error codes. So, revisit the CImageSource, a la error codes: class CImageSource { public: CImageSource (); virtual ~CImageSource (); virtual int GetImage (Image &img) = 0; }; Here, GetImage() will return an integer error code. The first thing you may notice is there are no messages inherently associated with error returns. You can now take two approaches, both with issues. The first approach: // Error codes; or you could use const int, enum, whatever you prefer: #define IMGSRCERR_OK 0 #define IMGSRCERR_NULL_POINTER 1 // Etc... class CImageSource { ... // given an error code, return a string static string ErrorStr (int errcode); ... }; A few things you should immediately notice. First of all, in order for ErrorStr() to work properly, all error codes must be unique. Therefore, either individual subclasses must be coded with awareness of the other error values in mind (so no conflicts are created for device-specific error codes), or, alternatively, some very general broad set of common errors must be defined that any device-specific errors would fall into (for example, IMGSRCERR_NOTREADY if an image source device was "not ready", which doesn't necessarily apply to all sources anyway). The issue with the former is maintenance, although if you can keep it up, then it's a reasonable solution. The issue with the latter is vagueness. One common solution I have seen to these problems is to use, say, 32-bit error codes where the high 16-bits are some sort of general "class" and the low 16-bits are a specific error code. In any case, to avoid the vagueness, the implementation of ErrorStr must be aware of all of the subclasses and their error codes. This has already complicated things over using exceptions, where this requirement does not exist. I mentioned that you could take two approaches to associating strings with error codes. The second approach is this: class CImageSource { ... // Given a source-specific, return a string. virtual string ErrorStr (int errcode); ... }; In that approach, each subclass is responsible for defining it's own error codes and strings. The base implementation could return a generic message, as well, so unknown codes could be deferred to the base: string CHTTPImageSource::ErrorStr (int errcode) { string msg; if (errcode is known) msg = the message; else // let base handle unknown/commong messages msg = CImageSource::ErrorStr(errcode); return msg; } This solves most of the problems above, but introduces a different collision problem where different error codes may mean entirely different things for different devices. Again, unless each subclass is aware of the other subclass's error codes, you will have issues. An example of the issues here is a function that grabs an image from two sources and does something with them, where you want the error handling mechanism to be defined by the caller. First with the error code interface above: int HandleImages (CImageSource *a, CImageSource *b) { Image x, y; int err; err = a->GrabImage(x); if (err != IMGSRCERR_OK) return err; err = b->GrabImage(y); if (err != IMGSRCERR_OK) return err; DoStuff(x, y); return IMGSRCERR_OK; } And now if the caller wants to display a message box, well... using the static ErrorStr as above it could do this: int err = HandleImages(a_, b_); if (err != IMGSRCERR_OK) DisplayError(CImageSource::ErrorStr(err)); If you chose the virtual ErrorStr() interface above, you're outta luck; you either have to return the error string itself from HandleImages() or somehow pass information back about which caused the error. Using exceptions, of course: void HandleImages (CImageSource *a, CImageSource *b) { Image x, y; a->GrabImage(x); b->GrabImage(y); DoStuff(); } And to take appropriate action: try { HandleImages(a_, b_); } catch (Exception &x) { x.GUINotify(); } Using exceptions, every problem above has been avoided, and the code has a much higher ratio of work to error-checking as well. It actually gets worse than that in this example. One thing that you may have noticed above is that no checks for errors in DoStuff() are performed. Using error codes, your HandleImages() function must check the return value of DoStuff() and pass it back to the caller. This now multiplies the set of problems described above: - HandleImages() must either return an error string or return info about which of the 2 GetImage calls and DoStuff returned an error. - DoStuff()'s implementation must be aware of the CImageSource subclasses so that it's error codes do not overlap. - Another way of mapping DoStuff() error codes to strings must be present (if using static ErrorStr the obvious solution to this is to make ErrorStr be a global function, not a CImageSource static member, of course -- the problems still exist, though). On the other hand, using exceptions, the code to handle DoStuff() errors is *identical* to the code above. DoStuff() throws an exception, the caller catches it and takes the appropriate action. You do not need to think about any of these difficulties. You do not need to add any extra handling logic. Everything just works. There is another solution to the HandleTwoImages() problem above that I sometimes see implemented. This solution is to allow each CImageSource (for example) to define it's own error codes. Then, define a global set of error codes, some of which duplicate the meaning of the errors defined by each individual CImageSource but have different values. Finally, provide a function to map specific error codes to global ones. You may think this sounds crazy, but this is actually done very frequently, although generally not for error codes within an application itself. Consider the actual implementation of CFOO1200ImageSource, for example. Perhaps the FOO-1200 drivers were written in C, and all functions return error codes. You have no control over the values of these error codes, but you are interested in their specific meanings (for example, the FOO-1200 API defines it's own error code for "file not found", etc.) in that you want to display the correct error message to the user. If you want to handle specific driver error codes as special cases, you must map the driver error codes to equivalent error codes in your application. In the most unfortunate case, the driver may even provide an API call to retrieve the last error string -- however you do not get to take advantage of this function as you must pass application error codes back to the application. Using exceptions can give you a big advantage here. If the FOO-1200 drivers do not provide a driver error -> string function then no, exceptions will not make the special case handling any more convenient -- you must still convert driver error codes to exception message strings per-error. However, if the FOO-1200 drivers do provide error strings, handling errors with exceptions is as easy as using the string returned from the drivers in some EFOO1200DriverException. No special case handling necessary, you do not need to be aware of error codes at all, there is no transformation from FOO-1200 error space to application error space, there is no massive lookup table in the application's ErrorStr() function (for example), there is nothing. Those are all problems that may come into play specifically when implementing CHTTPImageSource and CFOO1200Source, and the others. With exceptions, you can use the minimal amount of device/library-specific error code -> exception conversion at the lowest level, and after that, it all just works. Returning to GrabAndDisplayImage; the new implementation using error codes (compare this to above implementation using exceptions): int UI::GrabAndDisplayImage (CImageSource *source) { Image img; int ret; if (!source) return IMGSRCERR_NULL_POINTER; ret = source->GetImage(img); if (ret != IMGSRCERROR_OK) ret = display_->DisplayImage(img); return ret; } void UI::OnButton () { int err = GrabAndDisplayImage(source_); if (err != IMGSRCERR_OK) DisplayError(ErrorStr(err)); } It's still easy to read, sure, in this simple example. However, it suffers from every problem described above (and the same collision vs. vagueness issues now come into play in the implementation of DisplayImages() as well) -- the amount of thought and care that actually had to go in to selecting error codes and implementing ErrorStr() and friends in this case is incredible. Also something you mat notice. GrabAndDisplayImage now returns IMGSRCERR_NULL_POINTER if source is NULL. The ErrorStr() function likely changes this to a message such as "NULL parameter to function". It must, in order to cover the general case of IMGSRCERR_NULL_POINTER. The implementation with exceptions, however, threw an ENullPointerException (thus identifying the general error type) but with a specific, meaningful message that makes sense in context ("No image source specified"). You could change the error return code to IMGSRCERR_NO_SOURCE_SPECIFIED if you want; but then you have lost the information that it is an error related to NULL parameters being passed to a function. You could also check for IMGSRCERR_NULL_POINTER and display the appropriate message in OnButton() -- but that only works if IMGSRCERR_NULL_POINTER can't be returned for any other reason. With error return codes, there is no straightforward way to associate context-specific messages with general errors. You must take all this into consideration when using error return codes, or else you start giving the user messages like "Error: I/O error". With exceptions, you do not have to put any of this kind of thought into it. Thus concludes that example. I may have left something out, but I feel I have said enough to make my point clear. I would not argue whether or not you "should" use exceptions or "should" use error codes -- I am a major proponent of using whatever tool is most appropriate, most convenient, and most familiar to get a job done adequately. Proper error handling is possible with both methods. Also the above examples are not the only options -- there are many other ways of expressing the same things. I wanted to present a relevant example with the hope of showing that preferring exceptions over error codes can make life a lot simpler while coding. There is another example I want to point out, much more briefly: One thing that you can not do with error codes is return errors from class constructors. In some cases, this can lead to more complex invariants on classes and therefore more complex logic to determine the state an object is in. For example, let's see we have a class ImageBuffer that can hold some data. Using exceptions, I can do this: class OutOfMemoryError : public BaseException { ... } class InvalidDimensionError : public BaseException { ... } class ImageBuffer { public: // allocates buffer of appropriate size ImageBuffer (unsigned w, unsigned h) throw (OutOfMemoryError, InvalidDimensionError); ~ImageBuffer (); }; Using exceptions allows me to abort construction of an object on error. Therefore it is now possible for me to say "if an ImageBuffer exists, it encapsulates a valid block of memory of the appropriate size... period." It then becomes reasonable for me to assume that if I have an ImageBuffer I can use it safely: void ProcessImages () throw (BaseException) { ImageBuffer a(1000, 1000); ImageBuffer b(1000, 1000); // do stuff } I do not need to verify the state of the ImageBuffers in ProcessImages. Rather, as long as the caller is handling exceptions appropriately, everything is taken care of if one of the memory allocations fails. John Love-Jensen and Ted Byers may likely state that this is a good example of using exceptions to handle a rare, fatal -- that is, a memory allocation failure. That is true, but keep in mind the memory allocation error is used as an example, perhaps a constructor loads data from a file and throws an exception if the file does not exists (e.g.) -- this example applies to all of those cases of "predictable, common errors" as well. On the other hand, if I am to avoid exceptions I can no longer state the simple invariant that "if an ImageBuffer exists it is valid". Now, an ImageBuffer may exist but not be valid. I may implement it like this: class ImageBuffer { public: // possibly allocate buffer of appropriate size ImageBuffer (unsigned w, unsigned h); ~ImageBuffer (); int LastErrorCode () const; }; Now I must jump through the same hoops as in the CImageSource example above, and additionally I must always check the state of ImageBuffers (for example, if ImageBuffer has some member function like RotateImage(), it can not assume valid data, it must check to ensure that at least some conditions are true before doing it's thing): int ProcessImages () { ImageBuffer a(1000, 1000); ImageBuffer b(1000, 1000); int e; if ((e = a.LastErrorCode()) != 0) return e; if ((e = b.LastErrorCode()) != 0) return e; // do stuff } Exceptions handle all this logic for you. > One way to have more "in your face" return codes, is to return "smart return > code objects". > > A smart return code object has this kind of behavior: > + wraps a return code > + if the caller does read (query) the return code object, > THEN the object marks itself as having been checked > AND the object's destructor is silent > + if the caller does not read (query) the return code object, > AND the return code object does NOT have an error status, > THEN the object's destructor is silent > + if the caller does not read (query) the return code object, > AND the return code object DOES have an error status, > THEN the object's destructor does "something" > Where "something" is one or more of... > + an assert > + a trace to a log > + throw an exception :-) I hope that you realize how close to a properly designed exception class this "smart return code" is. See my example above for the parallels. The advantage that exceptions as used above have over the "smart return code" you have defined here is they do not suffer from the collision vs. vagueness problem that I described above. Using "smart return codes" still requires unique and meaningful error returns to be defined. Additionally, your description of a "smart return code" has a lot of special logic in it. I see a lot of +, AND, and THEN in there. None of that is necessary when using exceptions. Construct the exception and throw it. If an exception is caught it was an error. Most of the logic in your smart return code seems to allow for the propagation of the error code up the call chain until it is able to be handled. Exceptions handle this implicitly. Consider: void SomeFunction () throw (SomeException) { try { AnotherFunction(); } catch (...) { throw; } } It does not matter how deep in the call stack the original exception originates from. Eventually it will be thrown out of AnotherFunction(). SomeFunction() then has the option to catch it, perform it's own part of the recovery process, and then continue propagating the error upward until it is resolved. SomeFunction() need not even rethrow the exception -- if SomeFunction() has enough information to recover from the error completely, it can stop it right there and continue on it's way. > I consider a "smart return code object" to be training wheels for result > codes. But for larger teams, they become very helpful. The smart return code object as you have defined it could very well be training wheels for result codes. Proper exceptions provide far more functionality than smart return codes and error codes. The smart return code is, in fact, a sort of learning-disabled sibling of an exception. I mean that in a humorous way. For larger teams, smart return codes become helpful. Similarly, exceptions become even more helpful, as they provide all of the benefit of the smart return code, plus some. To be honest, while I am not setting out to convince you of one way or another, I do want to point out that it seems to me that you do, in fact, see the merit of using exceptions for common, predictable errors. However, for whatever reason, you are hiding this behind the "smart return code" mask -- I am guessing that the reason is because your philosophy is to only use exceptions on fatal errors, but that exceptions do provide elegant solutions to non-fatal problems, except to use exceptions directly would violate the philosophy (and so you have made your own constructs with closely related functionality). I think that you would have a lot to gain by beginning to favor proper exceptions over "smart return codes". They're almost the same thing! For the most part, everything you go on to say is completely reasonable and/or I addressed it above, although I do have some minor comments: > > And what do you consider to be unpredictable and rare? > > Any violation of the routine's contractual requirements. > > For example, if an int parameter must have the value of 0, 1 or 2, and the > value passed in is something other than 0, 1 or 2. > > Any violation of the routine's contractual obligations. > > For example, if the routine must put the object in state A or B, but was > unable to do so and the object is in state C (a "never happen" situation). > > For example, if the object is in an inconsistent state upon return, such > that the object's invariance is violated. This can happen if a state change > cannot be completed as a transaction, and enough has happened such that the > state of the object cannot be reverted. (That's why have transaction based > assignment operator -- perhaps using the swap paradigm -- is so very > useful.) Using exceptions does not make the swap idiom less useful; it provides implicit exception safety in the same way it provides good error return safety. > Any "never happen" situations. (Where, in the case of my applications, > keeping in mind that throwing an exception means "terminate the application, > forthwith". So throw an exception where it is appropriate to terminate the > application works as a rule-of-thumb in my application. Your exception > usage policy will likely be less extreme.) > > Any "is it okay if the processing continues (without throwing or returning > an error code) with the detected 'broken axle' condition?" If it's not > okay, then either throw or return an error code. > > > > If I was writing code to parse that, and found "root", then > > "something", then "value", but not "data", I would call that a rare... > > I presume you are speaking of the "meta-layer" (the decision making layer) > above the parser itself. > > In my application, ponder "is this failure such that the application should > be terminated ... or can it be handled in some reasonable failsafe fallback > and the application continue running?" In the case of the XML example I gave, let's say that not finding the "data" node was not fatal to the application. Perhaps the user attempted to load a file from a menu in a GUI and that failed. In this case the reasonably failsafe fallback is to notify the user of precisely the condition that caused the error. Nothing else can be done no matter what the error: the user must load a different file or somehow fix the offending document with other means. Throwing an exception with a descriptive error message from the source of the error, then propagating it up the call chain until something can handle it appropriately (in this case, by displaying the message in a message box to the user), handles this elegantly, no matter what the source of the error. Of course this all relies on constructing exceptions with useful information in them to begin with. One important point that this brings to mind is; poorly designed exceptions will gain you *nothing* over error return codes. In fact, you may lose something. Take, for example, the Something()'s in my first message. If used as-is, with no parameters, and if all error conditions throw a Something(), then really, what can you do with that? The most you can do is display a message that says "Somewhere, at some point, some error occurred". That is unacceptable. Exceptions in themselves do not gain you anything, but exceptions give you a great framework for simple and powerful error handling and recovery. Just with any other language construct, "improper" use will render its potential benefits meaningless. > Also useful is writing your own throw handler such that if a throw happens > it forks the application and core dumps the child (useful to have > "core.<PID>" files enabled). That's how rarely I expect an exception to > occur, even when using exceptions with less draconian policies than > "terminate the application, forthwith". And with a core you have a good > snapshot of the application and where things went awry. Indeed. However, I want to point out that this can also be accomplished while still using exceptions for lesser errors. Take, for example, the EBaseException above, which contains some information about the severity of the error. It would be straightforward to flag exceptions that should "terminate and dump core" as such. Or when using the "Recover()" interface that I had mentioned, perhaps you have one of these: class EFatalError : public EBaseException { public: virtual void Recover () { // ... terminate and dump core ... } } Of course this makes "Recover" a bad name for the function ;-) let's call the function "Handle()" instead. Now any exception deriving from EFatalError could cause the application to terminate, if you choose to structure your application this way. You can also make the default implementation of "Handle()" simply display a message box, and now you have this elegant solution: void DoSomething () { try { SomethingThatCouldThrow(); } catch (EBaseException &x) { x.Handle(); } } And that code displays a message box if appropriate, or dumps core if appropriate, or whatever Handle() does for the exception that was thrown. > Also, once you start using exceptions, you have to be careful too. > Exceptions cannot be thrown through a C barrier. That means that you cannot > propagate an exception through an OS callback or other C callbacks. You > cannot propagate an exception outside of a thread's entry routine. And on > some platforms, you cannot propagate an exception out of the "module" (DLL) > that generated the exception. All of this is entirely true. It's a problem that I am familiar with. There are clever ways to pass exceptions around these boundaries, that I have used. Error return codes can be passed across such boundaries with much less of an issue. However, perhaps it is the nature of our respective work, but I find that the cases where I run into these boundaries are so relatively rare, that I can not justify them as reasons to sacrifice the other benefits of exceptions in an application. In the case of the C barrier, your only real choice is to keep the errors as error codes at the lowest level, and only translate to exceptions on a higher level once you back on the C++ side. This is not always possible, of course, but some things can make it very easy: for example, if whatever C API you are using allows your callback function to return an error code. If the callback in question can cause some sort of status to eventually be returned to the C++ side of things, then you can deal with throwing exceptions there. If the callback can not cause some sort of status to be returned, you are out of luck -- although IMHO the problem here is more of a shortcoming of the API you are using. This flavor of solution is also necessary when the module or threaded code is in C as well. For thread entry functions and DLLs, there many solutions as well. Unfortunately you do have to code with awareness of these boundaries in mind. You could keep errors as error codes on the other side of the boundary, throwing exceptions only when able. This is what you would be doing anyways if you were not using exceptions -- you would be using error codes. Also exceptions are objects just like any other, in the past I have had a lot of success having threads that must throw exceptions out of the thread handler store the exception and do no further processing, and at the next available opportunity pass that exception object to whatever thread *can* handle it, which then proceeds to throw the exception (another solution is to have the thread return a pointer to a new exception object, and defer handling until the thread terminates) -- in any case error handling in threads and DLLs using exceptions has all of the same design caveats as passing any other complex data around. How you get across these boundaries really does depend on the situation. Again you have to use the right tools for the job. You can not throw exceptions out of thread handlers, and so error codes will help you out more there, at least until the error gets to the point where you can throw. > > Take Java as an extreme example of the use of exceptions. > > Java exceptions are not the same thing as C++ exceptions. > > In my opinion, Java exceptions are much more useful, and robust, and can be > used in Java situations where one would not use C++ exceptions in analogous > C++. Can you think of an example that supports this? I do not think that is a true statement. I think it is more accurate to say that because exceptions were an important part of Java to begin with, much of the Java language and APIs has been designed with them in mind, and therefore they are useful because they are used more frequently and more effectively. Java exceptions give you nothing that C++ exceptions do not have. The major problem with C++ exceptions is that they are frequently used ineffectively -- not that they must necessarily be ineffective. For example, Java strictly enforces the throws() clause of a function. It has done so from the start. Therefore the throws() clause is a very important part of a a function in Java, it carries a lot of meaning. In C++, it is very rare to find a compiler that even *attempts* to have some support for the throw() clause. I use it only as documentation, basically. Some C++ compilers even issue warnings along the lines of "throw() clause ignored" when you have it. This is one example of many reasons why a programmer might construct more useful exceptions in Java than in C++. Java is a very exception-oriented language. C++ supports them but the language itself doesn't really encourage their use in any specific way. Therefore I feel that many C++ programmers do not understand the benefits of using exceptions, and proper techniques for effective use, as well as a Java programmer might. On the other hand, C++ exceptions do give you that same type of functionality. One feature that I have always desired the most in a C++ compiler is strict enforcement of throw() clauses. Java exceptions are not inherently "better" than C++ exceptions -- rather, exception design in Java should be used as a reference model for a C++ programmer who also wishes to use exceptions effectively. > > on failure. In this case, exceptions are a great error return, because > > they can store a lot more information than, say, returning an integer > > from a function. In fact, this is *precisely* what exceptions are > > designed for. > > An error code from a function does not need to be an int. The error code > can be an object that can store a lot more information. If you start returning objects from functions on errors, you end up emulating the functionality of exceptions, but with a different syntax. Additionally, checking return types of functions rather than throwing an exception (which automagically branches to a catch, and also automagically is propagated up the call chain) ends up requiring error handling logic that can be avoided by using exceptions. Even more importantly, this begins to interfere with normal function return values. For example, with exceptions: int QueryA () throw (Exception); double QueryB () throw (Exception); These are easy to use, of course. The function declarations are easy to read and understand. Calling them is simple. It is very clear. Handling errors is also very clean: int a; double b; try { a = QueryA(); b = QueryB(); } catch (Exception &x) { // handle } The beauty is in the simplicity. You can use the return value from functions to pass info back. You can catch thrown exceptions to handle errors. As soon as you start returning error objects, you have to start adding unnecessary complexity. Even small, small amounts of complexity, such as this and all it entails: ErrorInfo QueryB (double &s); // returns error info Are unnecessary when using exceptions. > > Yes, I know this. This is precisely why I came here to ask about SEH > > in GCC... SEH is a feature in other compilers that I use precisely to > > prevent the bloat. GCC did not provide it, and now I am looking for an > > alternative. You are preaching to the choir, my friend. > > C++ has RAII, which is just as useful, and is standard. No need for an SEH > compiler extension. > > I'm glad you found Boost -- amazingly cool C++ enhancers. :-) It's true. There is some really awesome stuff here. > > Sincerely, > --Eljay > > Note: Ted Byers is spot on. I would have answered in exactly the same way > as he did, but I doubt I could have answered as eloquently. > > Note: Read Herb Sutter's Exceptional C++. Got it right here on the shelf, finished. :-) > Also, read Herb Sutter & Andrei Alexandrescu's C++ Coding Standards. I will check this out. I actually might have an eBook version of it laying around that I have been meaning to look over. Well, that concludes this week's article. Jason ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-21 2:11 ` Jason Cipriani @ 2008-03-21 2:37 ` me22 2008-03-21 2:45 ` Jason Cipriani 2008-03-21 15:24 ` Noel Yap 2008-03-21 22:50 ` Brian Dessent 2 siblings, 1 reply; 23+ messages in thread From: me22 @ 2008-03-21 2:37 UTC (permalink / raw) To: gcc-help On Thu, Mar 20, 2008 at 10:10 PM, Jason Cipriani <jason.cipriani@gmail.com> wrote: > On Thu, Mar 20, 2008 at 8:23 AM, John Love-Jensen <eljay@adobe.com> wrote: > > In my projects, throwing an exception mean "the application is about to > > terminate." > > > > That's using the exception mechanism at the extreme of conservative > > programming with exceptions. > > > > Assuming you use exceptions with less draconian policy, the exception > > mechanism is not for using as normal flow control. It really means an > > exceptional situation, outside of the normal flow control. > > > > For normal flow control -- such as handling predictable, common error > > conditions -- you should use return codes. > > It seems that the root of any disagreement is what kinds of errors > we'd prefer to represent with exceptions. You and Ted would use them > for rare, fatal error conditions, similar in spirit to machine > exceptions such as access violations and invalid instructions. I would > use them for more common errors such as invalid user input, missing > files, network errors, etc. I have a hunch nobody is going to be > having a change of heart any time soon. :-) One thing that I have > often noticed, incidentally, is that the longer a programmer has been > programming before C++, the more they prefer error codes to > exceptions. > Personally, I think that if something can't happen, like a divide by zero, is a case for an assert, not an exception. Leave them in the release if you want, but better, fix them! I prefer using exceptions for things that you'd rather never happened, and if everything does perfectly, never will happen. (Which also means that their performance doesn't matter.) Especially when those are the kinds of things that can rarely be handled locally. I like having a fairly clean code path. Also, exceptions thrown in constructors mean tighter invariants that you just can't get with return codes. 2-phase construction is evil. > [snip exceptionally long post that I didn't actually read, sorry :P ] > ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-21 2:37 ` me22 @ 2008-03-21 2:45 ` Jason Cipriani 0 siblings, 0 replies; 23+ messages in thread From: Jason Cipriani @ 2008-03-21 2:45 UTC (permalink / raw) To: me22; +Cc: gcc-help On Thu, Mar 20, 2008 at 10:36 PM, me22 <me22.ca@gmail.com> wrote: > On Thu, Mar 20, 2008 at 10:10 PM, Jason Cipriani > > <jason.cipriani@gmail.com> wrote: > > > On Thu, Mar 20, 2008 at 8:23 AM, John Love-Jensen <eljay@adobe.com> wrote: > > > In my projects, throwing an exception mean "the application is about to > > > terminate." > > > > > > That's using the exception mechanism at the extreme of conservative > > > programming with exceptions. > > > > > > Assuming you use exceptions with less draconian policy, the exception > > > mechanism is not for using as normal flow control. It really means an > > > exceptional situation, outside of the normal flow control. > > > > > > For normal flow control -- such as handling predictable, common error > > > conditions -- you should use return codes. > > > > It seems that the root of any disagreement is what kinds of errors > > we'd prefer to represent with exceptions. You and Ted would use them > > for rare, fatal error conditions, similar in spirit to machine > > exceptions such as access violations and invalid instructions. I would > > use them for more common errors such as invalid user input, missing > > files, network errors, etc. I have a hunch nobody is going to be > > having a change of heart any time soon. :-) One thing that I have > > often noticed, incidentally, is that the longer a programmer has been > > programming before C++, the more they prefer error codes to > > exceptions. > > > > Personally, I think that if something can't happen, like a divide by > zero, is a case for an assert, not an exception. Leave them in the > release if you want, but better, fix them! > > I prefer using exceptions for things that you'd rather never happened, > and if everything does perfectly, never will happen. (Which also > means that their performance doesn't matter.) Especially when those > are the kinds of things that can rarely be handled locally. I like > having a fairly clean code path. > > Also, exceptions thrown in constructors mean tighter invariants that > you just can't get with return codes. 2-phase construction is evil. > > > [snip exceptionally long post that I didn't actually read, sorry :P ] That's OK :-) I don't think I actually read it, either! I did actually specifically mention that throwing exceptions from constructors leads to tighter invariants. I think you pretty much said everything I said in that last post, but a lot more efficiently. Jason > > > ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-21 2:11 ` Jason Cipriani 2008-03-21 2:37 ` me22 @ 2008-03-21 15:24 ` Noel Yap 2008-03-21 22:50 ` Brian Dessent 2 siblings, 0 replies; 23+ messages in thread From: Noel Yap @ 2008-03-21 15:24 UTC (permalink / raw) To: Jason Cipriani; +Cc: John Love-Jensen, GCC-help, Ted Byers On Thu, Mar 20, 2008 at 7:10 PM, Jason Cipriani <jason.cipriani@gmail.com> wrote: > It seems that the root of any disagreement is what kinds of errors > we'd prefer to represent with exceptions. You and Ted would use them > for rare, fatal error conditions, similar in spirit to machine > exceptions such as access violations and invalid instructions. I would > use them for more common errors such as invalid user input, missing > files, network errors, etc. I would use them for network errors. Depending upon why files may be missing, I may or may not use them for that purpose. I would definitely not use them for invalid user input. I suppose where I would draw the line is whether or not the system has control over the inputs in question. For example, if a file is expected to exist because another system or a user is supposed to create the file, exceptions shouldn't be used. I will say, though, that this is how I use exceptions in C++. I'm much more lax with exception usage in languages like Python and possibly Java (I don't have that much experience with Java, but I did use exceptions for flow control in order to implement an ISO-8601 parser whose grammar seemed to require some lookahead, but I may just have lacked the skills to eliminate this (mis)use of exceptions). Noel ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-21 2:11 ` Jason Cipriani 2008-03-21 2:37 ` me22 2008-03-21 15:24 ` Noel Yap @ 2008-03-21 22:50 ` Brian Dessent 2008-03-21 23:14 ` Noel Yap 2 siblings, 1 reply; 23+ messages in thread From: Brian Dessent @ 2008-03-21 22:50 UTC (permalink / raw) To: Jason Cipriani; +Cc: John Love-Jensen, GCC-help, Ted Byers Jason Cipriani wrote: > One feature that I have > always desired the most in a C++ compiler is strict enforcement of > throw() clauses. Since you seem to be well aware of the design of C++ exceptions you should realize why this is a near impossibility. In order for the compiler to enforce a throw declaration, it would have to have complete knowledge of the entire outbound call graph of the function. Two things stand in the way of this: separate compilation and function pointers/virtual functions. The problem of separate compilation (i.e. that the compiler only has local knowledge of a given translation unit at any time) can somewhat be dealt with by using gcc --combine, but that's more of a crutch. It won't fix the issue that necessitated separate compilation in the first place: the explostion of complexity as the size of one "unit" grows. (And here I'm not speaking at all of the human factors of keeping source units small and maintainable, I'm strictly referring to the algorithmic complexity experienced by the compiler.) Longer term, the gcc LTO project will eventually provide a better infrastructure for whole-program optimizations, but it will still not be able to see past library boundaries. That might not be a problem if like you say C++ had been more Java-like and required accurate throw declarations from the beginning. But there is just no reasonable way to expect that now, so from a compiler standpoint the only way to implement enforcement of throw declarations would be to "see down into" all library code (unrealistic for all but perhaps embedded applications) or to rewrite all library code to include accurate throw declarations in their headers and then tell the compiler it can trust them. Either way, it's a ton of work. And this hasn't even begun to address the issue of function pointers and virtual methods, where the call graph can't even be known at compile time. I'm sure you could construct a pathological testcase without too much effort for which it would be provably impossible to enforce throw declarations at compile or link time. However, you may be interested in Brendon Costa's EDoc++ which consists of a hacked up gcc that embeds additional exception and callgraph data into the objects, and a post processing tool to recover, combine, and analyze it all at link time. <http://edoc.sourceforge.net/> Brian ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-21 22:50 ` Brian Dessent @ 2008-03-21 23:14 ` Noel Yap 0 siblings, 0 replies; 23+ messages in thread From: Noel Yap @ 2008-03-21 23:14 UTC (permalink / raw) To: GCC-help; +Cc: Jason Cipriani, John Love-Jensen, Ted Byers On Fri, Mar 21, 2008 at 3:50 PM, Brian Dessent <brian@dessent.net> wrote: > Jason Cipriani wrote: > > > One feature that I have > > always desired the most in a C++ compiler is strict enforcement of > > throw() clauses. > > Since you seem to be well aware of the design of C++ exceptions you > should realize why this is a near impossibility. In order for the > compiler to enforce a throw declaration, it would have to have complete > knowledge of the entire outbound call graph of the function. Two things > stand in the way of this: separate compilation and function > pointers/virtual functions. The problem of separate compilation (i.e. > that the compiler only has local knowledge of a given translation unit > at any time) can somewhat be dealt with by using gcc --combine, but > that's more of a crutch. It won't fix the issue that necessitated > separate compilation in the first place: the explostion of complexity as > the size of one "unit" grows. (And here I'm not speaking at all of the > human factors of keeping source units small and maintainable, I'm > strictly referring to the algorithmic complexity experienced by the > compiler.) > > Longer term, the gcc LTO project will eventually provide a better > infrastructure for whole-program optimizations, but it will still not be > able to see past library boundaries. That might not be a problem if > like you say C++ had been more Java-like and required accurate throw > declarations from the beginning. But there is just no reasonable way to > expect that now, so from a compiler standpoint the only way to implement > enforcement of throw declarations would be to "see down into" all > library code (unrealistic for all but perhaps embedded applications) or > to rewrite all library code to include accurate throw declarations in > their headers and then tell the compiler it can trust them. Either way, > it's a ton of work. > > And this hasn't even begun to address the issue of function pointers and > virtual methods, where the call graph can't even be known at compile > time. I'm sure you could construct a pathological testcase without too > much effort for which it would be provably impossible to enforce throw > declarations at compile or link time. > > However, you may be interested in Brendon Costa's EDoc++ which consists > of a hacked up gcc that embeds additional exception and callgraph data > into the objects, and a post processing tool to recover, combine, and > analyze it all at link time. <http://edoc.sourceforge.net/> I agree with all this and wanted to add: + C linkage (which may be included above) -- since C functions don't have exception specs, how should they be handled especially if they wind up calling C++ functions through callbacks + template functions -- what if the exception spec is dependent upon the template parameters? Noel ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-20 6:28 ` Jason Cipriani 2008-03-20 12:24 ` John Love-Jensen @ 2008-03-20 14:50 ` Ted Byers 2008-03-21 2:26 ` Jason Cipriani 1 sibling, 1 reply; 23+ messages in thread From: Ted Byers @ 2008-03-20 14:50 UTC (permalink / raw) To: Jason Cipriani; +Cc: gcc-help --- Jason Cipriani <jason.cipriani@gmail.com> wrote: > Thanks for the long response, Ted. Although I > disagree with most of > it, I appreciate your time. Some of the points you > have made are > slightly unclear: > Just a quick note. I won't answer all, since Eljay already did so brilliantly. I am not sure, though, why he regards our shared approach to exceptions and error handling as extremely conservative. > > Your error > > condition, then, doesn't arise. > > I am not sure what you mean. If a file does not > exist and therefore > can not be opened, notifying the user of the error > does not cause the > file to exist. > But the file the program attempts to open may not exist because the user had a little typo in the file name or path he entered. When he types the file name and path, before you do anything else, it is dirt simple to check to see if it exists, and if it doesn't, prompt the user to correct the file name or path before you attempt to do anything else. When you do this, you prevent any predictable error condition from happening at all. Look, C++ has a whole suite of error detection and handling capability. To use only exceptions is somewhat like a carpenter having only a hammer trying to hammer screws into place. If all you have is a hammer, everything looks like a nail. But if you have a set of screw drivers in your belt along with your hammer, you'll use the right tool for the job. > > User error WRT data > > input is common enough that it is much better to > test > > for all errors that you can test for, and prompt > the > > user for corrective action than it is to just > throw > > exceptions willy nilly every time a statement > doesn't > > give you what you wanted. > > Agreed. I don't see what is willy nilly. You're throwing everywhere. It is almost as if you have a bad case of cyber-stomache flu. You are using exceptions for flow control. That is not what they're there for. Eljay's brilliant description of return codes (including especially his smart return code objects) is a much better option. And don't forget assertions have a valid role in many cases also. > In this > particular example, I > have an XML file that contains some data that I need > to load. The XML > file must adhere to a very specific structure that > has been defined > elsewhere. When the user chooses to load the file, > the file must > either be 100% compliant with the defined structure, > or it will fail > to load. I have no desire to prompt the user with > message boxes such > as "Sorry, but the X data element was missing from > this data file, > would you mind telling me what you think the value > should be so I can > recover and continue?" I don't have a quarrel with you there. Doing that may well be a stupid thing to do. But you ought to have an idea as to what the user CAN do to address the problem. Or are you content with leaving the user in a situation where he can't finish his task because there is a problem with the data file you need to work with? Can the file be repaired? Regenerated? What is the user supposed to do when your program dies just because of a problem with the data file? But on the other hand, maybe the message box you reject out of hand is the right thing to do because maybe the user can find the missing data somewhere and enter it. I can't tell at this stage because I don't know your data processing stream. > A user would not be able to > answer that > question in my case anyway. Instead, if the file has > a slight problem > (it should not, as the files are not > hand-generated), I want to notify > the user that the file could not be loaded. Of > course, I would provide > them with a reason why the file could not be loaded, > but it is not > always necessary, or even possible, to allow the > user to intervene and > correct an error condition themselves. Therefore, in > the XML example, > if any one of those operations fails, the entire > load operation > *should* fail, and therefore every operation > warrants an error return > on failure. Then design an appropriate suite of error code objects, and return them. > In this case, exceptions are a great > error return, because > they can store a lot more information than, say, > returning an integer > from a function. In fact, this is *precisely* what > exceptions are > designed for. > Not quite. They do a lot more than return an error. I make extensive use of exceptions and exception handlers, but you will never find a case in my code where I use them for flow control. At this point, you need to step back and look at the bigger picture. The person using your program has a job to do, and your program ought to be making it easier for him to do it. If an error condition arises, what does he do? Just give up on getting the job done? He'd not last long if he told the boss he couldn't do his job because your program choked on a file. His employer would rightly expect that he do whatever is necessary to fix the problem and carry on. It may well be correct that the user wouldn't have the knowledge or information required to provide the detailed data expected at some point in your file, but you say that the file is not generated manually. As the designer of your software, you have to determine what your user can do to get his job done even when your program detects a problem in the source data file. You have to design your data processing stream to be fail safe, or at least safe fail, so that what ever happens, the user can do something to recover and carry on to finish the job. If you allow your data processing stream to be designed in a way that it can happen that an unrecoverable error can occur, then your data processing stream is too brittle and needs to be revised so that the user can recover regardless of what happens. I know this isn't always easy, but it is necessary. Trust me, I have had to develop software for client's whose users were secondary school graduates with no IT experience at all. They would be baffled at seeing the kinds of error messages you and I would understand at a glance. Stepping out of the mind of a programmer into the mind of your user is probably one of the hardest things you'll have to learn to do, but it will pay dividends. With some kinds of users, the meanest thing you can do is abruptly terminate with a brief error message. In some of my programs, I had to go the extra mile and build in some primitive artificial intelligence, to guide the user through the process of correcting any one of a number of problems that could be guaranteed to arise, using their language, not mine. Yes, there are things they can not fix immediately themselves. My program would monitor, for example, the hardware that provided data feeds to the program. It is certain that such hardware would fail on occasion. The user is not in a position to fill in the missing data, but my program would a) let them know a problem had arisen, b) walk them through a process of applying whatever fixes they COULD try, and if the device itself had developed a fault, direct them to replace it (so they can continue to work while the defective unit is repaired), and they don't find themselves in a position of putting in several days or weeks on a task without knowing some piece of hardware had failed. That kind of waste or inefficiency can be expensive, and your clients will bless you or curse you depending on how well you prevent such waste in the use /operation of expensive equipment and wasted manpower. While ensuring errors are always handled, and consideration is given to what calling code needs to know, is part of the picture, there is much more to analysis of possible error conditions, and prediction, detection and handling of them. Exceptions have a role, as do return code objects, and assertions. But that is really the small picture that needs consideration in the context of who the clients and users are, what they know and what they need to do. The software is not complete until you can guarantee that the user can complete all tasks, that involve use of the software, that have been assigned by the client, regardless of what happens. Trust me, I am not trying to be mean or hard on you. I am just trying to pass on a few things you won't find in the text books, or even in those wonderful reference books by geniuses like Sutter, or Stroustrup or Josuttis or Meyers or Lippman. I can't praise the books by such authors highly enough, but there is much learned only by serving people, by developing software for them, for decades. In my experience, the vast majority of errors can be predicted, and therefore prevented, and that means flow control, and that in turn means primarily error return code objects. HTH Ted ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-20 14:50 ` Ted Byers @ 2008-03-21 2:26 ` Jason Cipriani 0 siblings, 0 replies; 23+ messages in thread From: Jason Cipriani @ 2008-03-21 2:26 UTC (permalink / raw) To: Ted Byers; +Cc: gcc-help Ted, I can't respond to everything right now, because I somehow managed to spend a full hour typing that last email. Hopefully some of what you say here, I have addressed there. There are two things in particular that I want to point out, though: The first: On Thu, Mar 20, 2008 at 10:49 AM, Ted Byers <r.ted.byers@rogers.com> wrote: > Look, C++ has a whole suite of error detection and > handling capability. To use only exceptions is > somewhat like a carpenter having only a hammer trying > to hammer screws into place. If all you have is a > hammer, everything looks like a nail. But if you have > a set of screw drivers in your belt along with your > hammer, you'll use the right tool for the job. I believe I addressed this in my recent post. Using *only* exceptions would be a mistake. However, using error codes when exceptions can make things a lot simpler is also a mistake. The same goes both ways, "to use only error codes" fits into that analogy as well. There is a happy medium, and there are some situations where exceptions are more appropriate, and some where error codes are more appropriate. I take the approach of using exceptions unless I am forced to do something else, for example, the following situations: 1) Where performance is critical and exceptions have too much overhead. For example, if I was writing, say, a function that intersected a ray with a cylinder to use in a ray tracer implementation (or whatever), I would certainly not have that function thrown an exception if the ray did not intersect. 2) Where exceptions simply can't be used, such as in the boundary conditions mentioned above (especially across C boundaries). In this case, error codes are the best tool, and eventually they can be used to throw exceptions at a higher level if that is appropriate. I do not support using only exceptions. I also do not support using only error codes. In college, my friends used to call me Jason "Use the Right Tool For the Job" Cipriani. Well, maybe not. My original question about SEH was in a situation where SEH was a great tool for what I was trying to do. The book I just wrote about exceptions is intended only to show a good example of when exceptions are a better tool than error codes. The second thing is: > You're throwing everywhere. It is almost as if you > have a bad case of cyber-stomache flu. LOL. :-) It's better than cyber-diarrhea I suppose. > You are using > exceptions for flow control. I need to address this because it is not correct. I am using exceptions for *error handling* flow control. All error handling involves some sort of trivial flow control. However, do not confuse this with using exceptions for things like this: void DoSomething (vector<int> &data) { int i = 0; try { while (true) { DoSomethingElse(data[i]); ++ i; if (i >= data.size()) throw int; } } catch (...) { } // do more stuff } NO. DO NOT DO THAT! (heh). That is *not* what exceptions are for at all. Exceptions are for error conditions that must result in special handling. I hope that I haven't given an example otherwise -- if I have I didn't intend for it to look that way. > That is not what they're > there for. Eljay's brilliant description of return > codes (including especially his smart return code > objects) is a much better option. And don't forget > assertions have a valid role in many cases also. Assertions are good for stating assumptions; they notify you when your assumptions are invalid and also double as documentation when reading code. However, doing something like this: FILE *f = fopen("file", "r"); assert(f); Is *not* a good use for assert(), for hopefully obvious reasons. I used fopen() as an arbitrary example, substitute your favorite function instead. I have read the rest of your reply, but I can not respond to it right now. I apologize. But I did read it. Thanks. Jason ^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: try, finally 2008-03-19 16:13 try, finally Jason Cipriani ` (3 preceding siblings ...) 2008-03-19 17:37 ` me22 @ 2008-03-25 22:28 ` Ian Lance Taylor 4 siblings, 0 replies; 23+ messages in thread From: Ian Lance Taylor @ 2008-03-25 22:28 UTC (permalink / raw) To: gcc-help "Jason Cipriani" <jason.cipriani@gmail.com> writes: > Does GCC have anything similar to the MS and Borland compiler's __try > and __finally keywords? Side stepping the whole thread, I'm just going to mention that implementing try/finally would be a good Google Summer of Code project for gcc. http://code.google.com/soc/ . Ian ^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2008-03-25 22:28 UTC | newest] Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2008-03-19 16:13 try, finally Jason Cipriani 2008-03-19 16:14 ` Jason Cipriani 2008-03-19 17:02 ` Tim Prince 2008-03-19 16:37 ` Brian Dessent 2008-03-19 17:31 ` Ted Byers 2008-03-19 17:37 ` me22 2008-03-19 19:09 ` Jason Cipriani 2008-03-19 19:09 ` me22 2008-03-20 5:50 ` Jason Cipriani 2008-03-20 13:30 ` Noel Yap 2008-03-21 2:27 ` Jason Cipriani 2008-03-19 21:04 ` Ted Byers 2008-03-20 6:28 ` Jason Cipriani 2008-03-20 12:24 ` John Love-Jensen 2008-03-21 2:11 ` Jason Cipriani 2008-03-21 2:37 ` me22 2008-03-21 2:45 ` Jason Cipriani 2008-03-21 15:24 ` Noel Yap 2008-03-21 22:50 ` Brian Dessent 2008-03-21 23:14 ` Noel Yap 2008-03-20 14:50 ` Ted Byers 2008-03-21 2:26 ` Jason Cipriani 2008-03-25 22:28 ` Ian Lance Taylor
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).