Exception safety question in C++

Started by
47 comments, last by dv 15 years, 10 months ago
Quote:Original post by Antheus
Quote:Original post by Rydinare

This brings up something interesting, because you can violate program state without emitting the exception. For example, the exception could be caught silently (or an error code ignored) but the program could be left in an awkward state due to it. Is that beyond the scope of the exception guarantees?


You break it, you buy it.

Not much else can be said here. Language cannot prevent programmer bugs.


[smile] Fair enough. I was curious if there might be a name for this, but you have a good point that this is really just a bug.

The reason why I mention is some of my coworkers have a nasty habit of trapping exceptions, using mechanisms such as catch(...), with no implementation, including not even logging. Every once in a while, it leaves the program in an inconsistent state. Other than this being less than desirable, I was wondering if there was a name for this anti-pattern. Perhaps, the sweep-it-under-the-rug philosphy? [smile]
Advertisement
and now from this discussion you can see why exceptions may make more problems than they prevent... to give an idea of what you have to worry about:

ptr1=new object();ptr2=new object();//more stuff//donereturn;


that simple code above is NOT exception safe! OMFG. to make it exception safe:
auto_ptr<object> tptr1, tptr2;tptr1=new object();tptr2=new object();//more stuff//doneptr1=tptr1;ptr2=tptr2;return;


now we are adding more junk on top of the stuff to worry about exceptions.... what I am about to say might be called blasphemy:

if new throws an exception, then you are totaled anyways, since that means one cannot allocate memory. My preference is to write my own new which never throws, but exit(-1) if it can't malloc, keep in mind that if a malloc fails, 99.99% time the whole program is hosed as most STL calls, iostream calls and such need to allocate as well, so you probably won't even be able to flush and close your files..

once you have that new does not throw, then weather or not something else can throw is easier to see, any C library does not throw, and most _good_ C++ libraries avoid throwing as well... for me, if a C++ library throws, unless the library is amazing, I seriously consider not touching it, since the decision on when and where to catch exceptions and what to do with them is a major, major pain in the ass.... from a library writers point of view they look great: throw an exception if something goes wrong, from a user point of view: sucks ass, have to catch them.

or just never catch them and view it as an asssert style error... ewww....

and there is worse: writing Symbian, where the exception handling mechanism is real mess to deal with (say the phrase clean up stack to a Symbian developer and watch the disgust go across their face)

there are those that love the idea of exception handling, me thinks they are nuts.

as for the rule that destructors cannot throw, that is a lot harder to guarantee:

if an object is complicated and uses another library, for example. If an object has a resource, releasing a resource, if something is ungood, can throw an exception. Logging can throw an exception (since their is a malloc underneath). For example the following is NOT exception safe:

class MyClass{public: static ostream *loggingStream; ~MyClass() {   if(loggingStream!=NULL)   {     (*loggingStream) << "LOG: descontructor called\n";   }  }};


Note most projects will have a logging macro, so often the code really looks like:

MyClass::~MyClass(){  LOG("descontructor called\n");}



it is not exception safe because often the log is written to a file or somewhere else that by writing to it can throw an exception! OMFG

lesson for me atleast, avoid it as making exception safe code that does little is a major pain, and the straight C programmers will say to us, geez, it would be better if you just checked the return value instead..

I am grouchy today.
Close this Gamedev account, I have outgrown Gamedev.
Quote:Original post by kRogue
and now from this discussion you can see why exceptions may make more problems than they prevent... to give an idea of what you have to worry about:

*** Source Snippet Removed ***

that simple code above is NOT exception safe! OMFG. to make it exception safe:
*** Source Snippet Removed ***

now we are adding more junk on top of the stuff to worry about exceptions.... what I am about to say might be called blasphemy:

if new throws an exception, then you are totaled anyways, since that means one cannot allocate memory. My preference is to write my own new which never throws, but exit(-1) if it can't malloc, keep in mind that if a malloc fails, 99.99% time the whole program is hosed as most STL calls, iostream calls and such need to allocate as well, so you probably won't even be able to flush and close your files..

once you have that new does not throw, then weather or not something else can throw is easier to see, any C library does not throw, and most _good_ C++ libraries avoid throwing as well... for me, if a C++ library throws, unless the library is amazing, I seriously consider not touching it, since the decision on when and where to catch exceptions and what to do with them is a major, major pain in the ass.... from a library writers point of view they look great: throw an exception if something goes wrong, from a user point of view: sucks ass, have to catch them.

or just never catch them and view it as an asssert style error... ewww....

and there is worse: writing Symbian, where the exception handling mechanism is real mess to deal with (say the phrase clean up stack to a Symbian developer and watch the disgust go across their face)

there are those that love the idea of exception handling, me thinks they are nuts.

as for the rule that destructors cannot throw, that is a lot harder to guarantee:

if an object is complicated and uses another library, for example. If an object has a resource, releasing a resource, if something is ungood, can throw an exception. Logging can throw an exception (since their is a malloc underneath). For example the following is NOT exception safe:

*** Source Snippet Removed ***

Note most projects will have a logging macro, so often the code really looks like:

*** Source Snippet Removed ***


it is not exception safe because often the log is written to a file or somewhere else that by writing to it can throw an exception! OMFG

lesson for me atleast, avoid it as making exception safe code that does little is a major pain, and the straight C programmers will say to us, geez, it would be better if you just checked the return value instead..

I am grouchy today.


Haha. I agree that you're grouchy. [razz] I disagree with your harshness towards exceptions. I'll amend one of your points. You said your issue with exceptions is not being aware of what can throw from a method. That, I agree with. What I would amend it to is being wary of cases where a potentially thrown exception is not documented.

IMHO, exceptions were invented to solve the problem of the average programmer ignoring error codes and error codes cluttering the interface of a method. Exceptions are harder to ignore, because when they happen, if you didn't handle it early enough... you will definitely know. I like Java's method of explicit checked exceptions. I think it solves a lot of problems with questioning what can and can't throw.

It's easy to guarantee that destructors won't throw. Methods that guarantee not to throw and thread procs are probably the only two areas that I'm okay with catch(...). If you catch an exception upon object destruction, the best you can do is to try to log it, if your logger is still available.
Quote:Original post by kRogue
once you have that new does not throw, then weather or not something else can throw is easier to see, any C library does not throw, and most _good_ C++ libraries avoid throwing as well... for me, if a C++ library throws, unless the library is amazing, I seriously consider not touching it, since the decision on when and where to catch exceptions and what to do with them is a major, major pain in the ass.... from a library writers point of view they look great: throw an exception if something goes wrong, from a user point of view: sucks ass, have to catch them.



Please ignore this advice. Any time someone suggests ignoring/avoiding fundamental language features then you know that they :
A) Don't know the language.
or
B) They are working on something highly specific where something doesn't make sense and for some reason have extrapolated that belief far beyond its usefulness.

Or as in this case some of both.

Quote:Original post by Rydinare
You said your issue with exceptions is not being aware of what can throw from a method. That, I agree with.

You can declare the thrown exceptions in the method header.
int function() throw(A, B, C){    // ...}
Quote:Original post by kRogue
if new throws an exception, then you are totaled anyways, since that means one cannot allocate memory.
I too would like to debunk that point of view. It's entirely possible that a program may simply attempt to allocate a ridiculously sized object, or an oversized array. For example a user may have provided absurd input which at some point along the way the program uses to decide how big of an array to allocate. The program then proceeds to attempt to allocate 999999999 items for example, and this throws an exception. However this does not by any means spell death for the program. On the contrary, provided the memory allocation failure is caught somewhere reasonable, the program can continue without problems.
"In order to understand recursion, you must first understand recursion."
My website dedicated to sorting algorithms
Quote:

Please ignore this advice. Any time someone suggests ignoring/avoiding fundamental language features then you know that they :
A) Don't know the language.
or
B) They are working on something highly specific where something doesn't make sense and for some reason have extrapolated that belief far beyond its usefulness.

Or as in this case some of both.



Just because I disagree with the cult of exception handling, in no way implies either of the 2 above. I would bet that I know C++ extremely well, having coding in the industry across many platforms for over 15 years.

Let's take a look at what exceptions were supposed to solve: people did not bother using/checking error codes from return values of functions, the result being that the code became smack. The solution was to make it that if something goes wrong that an exception is thrown, and thus forcing the developer to deal with that situation. However, exceptions cause more trouble than they solve because one has to:

1) be wary of heap allocated memory, if an exception is thrown before the pointer is "saved" somehow, then one leaks. The favorite method is auto_ptr<>, and in Symbian (which strictly speaking does not throw exceptions, but Leaves() ), is a cleanup stack where pointers are pushed and popped.

2) what the compiler has to do to generate exceptions.

3) threads and exception don't mix. nowadays most desktops are multi core beasts, and the number of cores continues to grow... within a few years expect the baseline to be over 4 cores on a desktop, to get the power of that, one needs to have threads.

4) mixed language environments and exception handling do not mix well either.

5) code testing and review: generating automated tests which test if code is exception safe is _hard_. Compounding the issue is that when one uses exceptions, one really should decide where most exceptions are caught, as adding try and catch blocks all over one's code is much worse that just checking error codes. With error codes, automated testing is easier, moreover the error gets better localized, i.e. to where the function is called. With exception handling, a thrown exception, where it is caught is anywhere above in the call stack.

so with (5), people say write "exception safe code" which is might more painful to do correctly. The best way to write exception safe code is to not throw an exception, *gee*.

exception handling in Java is a touch nicer because the environment has garbage collection, so one does not need to save pointers, but it still is a mess to deal with.


Quote:
I too would like to debunk that point of view. It's entirely possible that a program may simply attempt to allocate a ridiculously sized object, or an oversized array. For example a user may have provided absurd input which at some point along the way the program uses to decide how big of an array to allocate. The program then proceeds to attempt to allocate 999999999 items for example, and this throws an exception. However this does not by any means spell death for the program. On the contrary, provided the memory allocation failure is caught somewhere reasonable, the program can continue without problems.


where are you going to have to catch that exception then? the best place would probably right were you tried to allocate it, so then you have try/catch around your new's.... would have been better to either 1) check the number of units you are trying to allocate or 2) the old C standby check, for NULL (or since in C++ check of 0x0, since NULL is out of fashion). Moreover, by using error codes, where the bad alloc happens is localized within the source code. If you want your program to recover, you want to recover as soon as possible, which typically means at the caller of said function. Error code prorogation might look ugly to many, but it is
1) easier to test
2) easier to read

and remember you have to be kind of crazy to call C++ code beautiful. Here are some question to ask oneself if you really know exception handling and if you really are using it:

1) when has it helped me make easier to write code? do I catch the exceptions or do I just not care and let the program end with uncaught exceptions. How is that behavior any different that assert(0)?

2) when I am catching exceptions are there some I do not catch? what is the overhead of using exceptions? is my code really thread safe now?

3) have you unit-tested your exception handling catch blocks with exceptions thrown from a 3rd party library?

[soapbox question]
4) why are OS kernels (which are quite complicated pieces of software), compilers(very complicated), windowing systems not written with exception handling? if they were so great, why aren't some of the most complicated and important pieces of software using them?
Close this Gamedev account, I have outgrown Gamedev.
Quote:Original post by kRogue
[soapbox question]
4) why are OS kernels (which are quite complicated pieces of software), compilers(very complicated), windowing systems not written with exception handling? if they were so great, why aren't some of the most complicated and important pieces of software using them?


Not getting into whether or not exceptions are evil, but that particular part is just silly.
Since OS kernels are generally written in C, exception handling isn't an option. Because, you know, C doesn't have exceptions.
Compilers? Pretty much the same reason? I haven't checked, but I assume GCC is written in C as well. Microsoft's VC compiler is written in a mix of both, and I'm curious how you know that they don't use *any* exception handling.
Quote:Original post by kRogue


Just because I disagree with the cult of exception handling, in no way implies either of the 2 above. I would bet that I know C++ extremely well, having coding in the industry across many platforms for over 15 years.


I also disagree with cult of template use. When working on an embedded system using an antiquated compiler, I've had many problems getting even the basic expressions to compile. Ditto for standard library.

Lesson to all C++ programmers: Avoid templates and standard library like pure evil.

You mentioned symbian. Wouldn't it be more reasonable to express your experiences in that scope, not as if they generally applied to C++?

Quote:4) why are OS kernels (which are quite complicated pieces of software), compilers(very complicated), windowing systems not written with exception handling? if they were so great,


Yea, every weekend, when I write my OS kernels, I ask myself the same question.

Again - I will, for sake of simplicity, generalize that there are currently 2 OS kernels. Windows and Linux. There's others, but they have no mainstream relevance. Two (2).

Quote:
Quote:why aren't some of the most complicated and important pieces of software using them?

Since they are written in C.

Here's a fun weekend experiment: Go to Linux kernel boards, and ask if they plan on using C++? Bring a fire extinguisher.


-----------


Exceptions in C++ have several pitfalls which will surprise anyone coming from managed memory background. Reasons for this lie in the very nature of language itself.

Part of the problem lies in manual resource management. This can almost completely be alleviated using RAII.
Another part lies in inconsistent implementation. Depending on compiler, they may not be realized in same way.

Some would say this is madness.

I'd say This. Is. C++. We are all aware of the pitfalls, and the reason why people say use C++ where appropriate, not as the bestest language around.

But every single C++ feature (beyond C legacy) has problems in one way or another in certain context. The solution isn't to ignore it with passion. Use it where appropriate.
Quote:Original post by DevFred
Quote:Original post by Rydinare
You said your issue with exceptions is not being aware of what can throw from a method. That, I agree with.

You can declare the thrown exceptions in the method header.
int function() throw(A, B, C){    // ...}


This would make a lot of sense, but unfortunately throw specifications aren't very useful in C++ (they're immensely useful in Java). Sutter recommends against them in C++ Coding Standards because they have a runtime side effect, instead of a compile time one and generate extra code to check. Furthermore, if you specify a no-throw, many compilers will optimize to assume there are no exceptions for that method. If it does throw an exception, your method goes from propagating an exception to likely crashing during runtime. Finally, MSVC++ does not implement exception specifications as per the C++ standard. Exception specifications in MSVC++ are either throw() or throw(...). Any variant of throw(A) or throw(A,B) etc... is automatically translated to throw(...), which does nothing.

I would 100% agree if throw specifications in C++ didn't have these issues. Unfortunately, they do.

This topic is closed to new replies.

Advertisement