Sign in to follow this  

Exception safety question in C++

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

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

Recommended Posts

Hello folks, I have a question regarding "exception safe" functions: As far as I understand, a function can be called exception safe if it doesn't leak ressources. However, if that method allocates memory on heap via new, when must that memory be deleted so the function is still considered exception safe? During stack-unwinding (using smart pointers)? Or can it be as late as in the catch clause? And if the destructor of one of the allocated objects throws, does your function have to be able to fix this to be exception safe? Thanks for your admittance

Share this post


Link to post
Share on other sites
The most fundamental requirement for an exception-safe function is to leave the program in a valid state if the execution leaves the function as an exception (as opposed to normal returning). This means that, before an exception leaves the function, any allocated but unreferenced resources must be released (regardless of whether through RAII or a catch block).

A more advanced, and useful, requirement for an exception-safe function is to leave the program in the same state it was in when the function was called, if the execution leaves the function as an exception. This means that not only will all unreferenced resources be released, but any changes applied to the program state will be reversed before the execution leaves the function. Again, how you do this is irrelevant as long as it's done.

Share this post


Link to post
Share on other sites
Quote:
And if the destructor of one of the allocated objects throws


Last I remember, destructors aren't supposed to throw.

Quote:
As far as I understand, a function can be called exception safe if it doesn't leak ressources


Exception-safe function must preserve data integrity. This means more than just releasing the resources, it means going as far as being ACID compliant.

For handling dynamic allocations, auto_ptr is sometimes used. Other types of smart pointers can be used, but auto_ptr comes with language, and is adequate for this purpose.

Quote:
when must that memory be deleted so the function is still considered exception safe

A function either executes completely, or all the changes made are reverted back.

This means, if an exception occurs in during function call, it's as if it were never called before. This is a binary condition, there's no such thing as reasonable exception safety.

The Exceptional C++ covers this topic extensively.

Share this post


Link to post
Share on other sites
Quote:
Original post by Desperado
Hello folks, I have a question regarding "exception safe" functions:

As far as I understand, a function can be called exception safe if it doesn't leak ressources. However, if that method allocates memory on heap via new, when must that memory be deleted so the function is still considered exception safe? During stack-unwinding (using smart pointers)? Or can it be as late as in the catch clause?


It has to be done by the throwing function, because (a) that's the whole point (you clean up your mess and others clean up their mess); (b) the catch clause can't clean it up, because how is it going to know how?

Quote:
And if the destructor of one of the allocated objects throws


This doesn't happen. A throwing destructor means "I couldn't cause this thing to cease to exist properly". You have BIG problems if that's possible.

Share this post


Link to post
Share on other sites
There are 4 levels of exception safety:


  1. no-throw - the function never throws exceptions.

  2. strong exception guarantee - when the function exits via an exception it does
    not change program state and no resources are leaked

  3. basic exception guarantee - when the function exits via an exception it leaves
    the program in a legal state and no resources are leaked

  4. not exception safe - when the function exits via an exception no guarentees are made about the state of the program



The first 3 are collectively refered to as the Abrahams' guarantees (named after David Abrahams, of boost fame). You should always implement the highest exception guarantee you can reasonably accomplish.

So in answer to your questions:
Quote:

As far as I understand, a function can be called exception safe if it doesn't leak ressources.

That's required, but not sufficient, for exception safety.

Quote:
However, if that method allocates memory on heap via new, when must that memory be deleted so the function is still considered exception safe? During stack-unwinding (using smart pointers)?

Stack unwinding is fine, as is a catch clause within the function, but not catch clauses elsewhere.

Quote:
And if the destructor of one of the allocated objects throws, does your function have to be able to fix this to be exception safe?

Destructors should always always always implement the no-throw guarantee, but don't have to go so far as to make themselves safe if another destructor called within the first destructor throws (it's impossible to make a destructor safe for this anyway).

Share this post


Link to post
Share on other sites
@all: That answers most of my questions, thank you!

One final thing:

A function is exception safe, but not exception neutral if it preserves the program state but doesn't throw an exception to inform the caller, right?

Share this post


Link to post
Share on other sites
Quote:
Original post by Desperado
@all: That answers most of my questions, thank you!

One final thing:

A function is exception safe, but not exception neutral if it preserves the program state but doesn't throw an exception to inform the caller, right?


If it doesn't throw an exception, then it... doesn't throw an exception. That's the no-throw guarantee.

If code called from within the function throws an exception, and the function doesn't catch it, it's exactly the same - from the POV of these guarantees - as if the exception was generated locally.

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
Quote:
Original post by Desperado
@all: That answers most of my questions, thank you!

One final thing:

A function is exception safe, but not exception neutral if it preserves the program state but doesn't throw an exception to inform the caller, right?


If it doesn't throw an exception, then it... doesn't throw an exception. That's the no-throw guarantee.

If code called from within the function throws an exception, and the function doesn't catch it, it's exactly the same - from the POV of these guarantees - as if the exception was generated locally.


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?

Share this post


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

Share this post


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

Share this post


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

//done
return;




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
//done
ptr1=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.

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


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

Share this post


Link to post
Share on other sites
Quote:
Original post by kRogue
1) be wary of heap allocated memory, if an exception is thrown before the pointer is "saved" somehow, then one leaks.


RAII is meant to handle any resource leakage scenarios.

Quote:
Original post by kRogue
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.


Not necessarily true. You just need to be more cautious about how you code. This means that if you catch an exception which needs to propagate to a different thread, you need to be able to catch the exception and rethrow it in the other thread. This isn't an especially difficult technique, it just takes a bit of care.

Quote:
Original post by kRogue
4) mixed language environments and exception handling do not mix well either.


Untrue. Many language bindings provide means of translating C++ exceptions into exceptions in other languages. Check: Boost.Python and SWIG.

Quote:
Original post by kRogue
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.


I think that testing code for exception safety is a bit more difficult. The difficulty is in that it requires different methods than what people are used to.

Your opinion of adding try and catch blocks all over the place making things worse is simply your opinion. I find that code that correctly has the equivalent handling in both the error code and exception case tends to look cleaner and often has less lines of code in the exception case. But again, a messy design can make this look bad. From the sounds of it, it sounds like you're familiar with systems where exceptions weren't used properly.

Quote:
Original post by kRogue
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.


Explain how it's a mess in Java? Have you done much Java? I think in Java it's brilliant. For any checked exceptions, if a method states it will throw and I don't explicitly state my intention, by either announcing that I will allow it to continue propagating (adding it to the throws() spec of the method) or that I will handle it (using catch), it is a compile error. This means that I cannot make a mistake that I cannot detect at runtime where a checked exception I expected propagated further than I wanted.

As for the GC, again, Java provides a (less nice) flavor of RAII, by using the try-finally construct. Any code that needs to clean up resources needs to do so in the finally. To not do so is a bug.

Edit: I will say that some less experienced OO developers will silently trap exceptions and not handle them properly in Java, in order to not have to deal with exception specifications. Sweeping Under the Rug

[Edited by - Rydinare on May 30, 2008 8:40:30 AM]

Share this post


Link to post
Share on other sites
Quote:

Explain how it's a mess in Java? Have you done much Java? I think in Java it's brilliant. For any checked exceptions, if a method states it will throw and I don't explicitly state my intention, by either announcing that I will allow it to continue propagating (adding it to the throws() spec of the method) or that I will handle it (using catch), it is a compile error. This means that I cannot make a mistake that I cannot detect at runtime where a checked exception I expected propagated further than I wanted.


It is much nicer in Java, this I do not debate, but it still is a hassle. If you have a resource that you allocate, for example a graphics context, then when you throw you have to make sure that you release the graphics context. Now what happens in the Java language with the the control of prorogation of exceptions: if a function you call throws, then either you let it propagate and make sure you have your finally block good, then you have to make sure that you guarantee the objects state is still good if a function you call throws, that can be tricky for more complicated object. In the second case, it is just a syntax enforcement of checking error codes of functions you call, it might look nice to write everything in one try block and have a catch block below, but often depending on who threw changes what you need to do. The finally block is a syntax construction necessary because of the icky nature of exception handling, moreover if you have several things you need to release, you need log in place that either lets you release stuff you don't have or to make sure you only release what you have...the second case is a major PITA.

Quote:

Not necessarily true. You just need to be more cautious about how you code. This means that if you catch an exception which needs to propagate to a different thread, you need to be able to catch the exception and re-throw it in the other thread. This isn't an especially difficult technique, it just takes a bit of care.


it is a much bigger mess than that; the two threads have their own stacks... so if the thread A catches an exception then it needs to tell thread B to throw it, which means that thread A needs to send a message to thread B "throw this". what happens in thread A? should it get killed, what should its state be? these are all hard questions. what about where does thread B decide to throw the exception, remember most of the point of exception handling is the stack unwinding....

Quote:

Untrue. Many language bindings provide means of translating C++ exceptions into exceptions in other languages. Check: Boost.Python and SWIG.


SWIG themselves state that making it work cross-platform is touch and go, moreover, SWIG has a great deal of limitations. And as for Boost.Python, the docs say "All C++ exceptions must be caught at the boundary with Python code." so here we have to translate between C++ and Python exceptions...


Quote:

Your opinion of adding try and catch blocks all over the place making things worse is simply your opinion. I find that code that correctly has the equivalent handling in both the error code and exception case tends to look cleaner and often has less lines of code in the exception case. But again, a messy design can make this look bad. From the sounds of it, it sounds like you're familiar with systems where exceptions weren't used properly.


which more or less means that at the planning stage, it is decided where exceptions are caught, and one is not suppose to add to that list (I am all for this!). But now what happens, if a function calls a function that can throw it needs to guarantee that the state is ok, this is particularly obvious if one has member functions that can throw. Writing this kind of code can be subtle, because you need to make sure that object is in a state you can deal with in a reliable fashion. Some idiots like to add booleans to say "object's member function foo() in middle of call" and to clear that boolean and the end of the function call... this is just stupid, as it is error prone and makes for particular messy and painful cleanup code.

At the end of the day, what I am claiming that the idea of exception handling was to cut down on errors, increase reliability and have developers type less. But now with it, we get a whole messy can of worms to deal with because of its automatic nature. I can trust a much less experienced developer to correctly write "check return code" source than to write correct exception handling source, as the former is much easier to write, review and test. If a developer does not check their return codes, they are not average, they are incompetent.



Quote:

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


*cough* for Desktops, have you heard of Mac OS X? for a great deal number of devices and servers have you heard of BSD? and Windows "kernels" means different things: Win9x kernel (eww), NT kernel (many versions NT3.x, 4.x, 2000, XP, Vista)

and the action is not really in the desktops nowadays, the cutting edge is small devices and embedded devices, there you find many kernels, many of them custom for the device. One such example is Symbian, which should be burned from humanity as it is an abomination.


Quote:

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.


y
For exception handling, I fully recommend not to use it. To use error codes and check for them. Code is easier to review, write and test. Yes it does not look "clean", but it gets the job done reliably and with less work. I cannot be that off my wanker, as many compilers include optimization flags to turn exception handling off, unless one wants to argue that the compiler writes are nuts as well.

Quote:

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.


You did see the tongue in cheek [soapbox question] remark, right?
I will confess I don't actually know if MSVC compiler does not use exception handling, but for those sick enough, you can check. MSVC compiled code's exception handling comes to some degree from the OS (I leave you to use google for that) and with some debug tools one can see if CL.exe has the junk that says it is using the exception handling of Win32/64 systems.. admittedly they could use another implementation, but I would find that hard to believe. My point is that very complicated robust systems have been made, and work reliably, without exception handling.

and now because I am grouchy, I need to flame stonemetal:

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.



writing something like the above, rather than giving technical arguments and points (as everyone else has) indicates either one does not have the proficiency to defend your opinions (which implies that at best you have opinions at worst opinions with no technical merit) or just being lazy. Neither is good for discussion. Moreover, I would not call my advice "ignore exception handling" I would call it, beware of it, for it creates more headaches than it tries to solve. Lets do with this: almost everyone says that "avoid goto" like the plague, but goto is a language feature in C. Following your quote, I guess almost nobody understands C or only works in very specific areas. I pick on goto because exception handling can be viewed as a stack safe goto, i.e. goto the most recent catch block (strictly speaking that is not really comparable to goto, since goto must be in the same function, but is more akin to longjmp).

Lets review what exception handling was supposed to do for the developer:

after each function, if you got there then the function did its job and nothing bad happened, so you can continue and assume nothing went wrong, because if something did an exception would be thrown. This is great, now we do not have to litter our code with status code checks and promotion of error codes, i.e:


statusCode=foo_function();
if(statusCode indicates an error)
{
//handle error or return error code to indicate ungoodness.
}



When an exception is thrown, the state should be restored to the point of the most recent catch block.

But doing this is where all the worms come: guaranteeing that the state is restored is more than just letting deconstructors get called, it is usually much more subtle in any complicated project. If an exception gets thrown while in the the middle of a complicated algorithm, it is _hard_ to make sure that the object and data will be in a good state when stuff gets thrown. Moreover, there is a significant performance penalty if throwing exceptions versus checking error codes, this is also important when you are pushing your hardware, and we are supposed to be doing that in writing games, right?

[Edited by - kRogue on May 30, 2008 2:41:51 PM]

Share this post


Link to post
Share on other sites
Quote:
Original post by kRogue
Quote:

Explain how it's a mess in Java? Have you done much Java? I think in Java it's brilliant. For any checked exceptions, if a method states it will throw and I don't explicitly state my intention, by either announcing that I will allow it to continue propagating (adding it to the throws() spec of the method) or that I will handle it (using catch), it is a compile error. This means that I cannot make a mistake that I cannot detect at runtime where a checked exception I expected propagated further than I wanted.


It is much nicer in Java, this I do not debate, but it still is a hassle. If you have a resource that you allocate, for example a graphics context, then when you throw you have to make sure that you release the graphics context. Now what happens in the Java language with the the control of prorogation of exceptions: if a function you call throws, then either you let it propagate and make sure you have your finally block good, then you have to make sure that you guarantee the objects state is still good if a function you call throws, that can be tricky for more complicated object. In the second case, it is just a syntax enforcement of checking error codes of functions you call, it might look nice to write everything in one try block and have a catch block below, but often depending on who threw changes what you need to do. The finally block is a syntax construction necessary because of the icky nature of exception handling, moreover if you have several things you need to release, you need log in place that either lets you release stuff you don't have or to make sure you only release what you have...the second case is a major PITA.

Quote:

Not necessarily true. You just need to be more cautious about how you code. This means that if you catch an exception which needs to propagate to a different thread, you need to be able to catch the exception and re-throw it in the other thread. This isn't an especially difficult technique, it just takes a bit of care.


it is a much bigger mess than that; the two threads have their own stacks... so if the thread A catches an exception then it needs to tell thread B to throw it, which means that thread A needs to send a message to thread B "throw this". what happens in thread A? should it get killed, what should its state be? these are all hard questions. what about where does thread B decide to throw the exception, remember most of the point of exception handling is the stack unwinding....

Quote:

Untrue. Many language bindings provide means of translating C++ exceptions into exceptions in other languages. Check: Boost.Python and SWIG.


SWIG themselves state that making it work cross-platform is touch and go, moreover, SWIG has a great deal of limitations. And as for Boost.Python, the docs say "All C++ exceptions must be caught at the boundary with Python code." so here we have to translate between C++ and Python exceptions...


Quote:

Your opinion of adding try and catch blocks all over the place making things worse is simply your opinion. I find that code that correctly has the equivalent handling in both the error code and exception case tends to look cleaner and often has less lines of code in the exception case. But again, a messy design can make this look bad. From the sounds of it, it sounds like you're familiar with systems where exceptions weren't used properly.


which more or less means that at the planning stage, it is decided where exceptions are caught, and one is not suppose to add to that list (I am all for this!). But now what happens, if a function calls a function that can throw it needs to guarantee that the state is ok, this is particularly obvious if one has member functions that can throw. Writing this kind of code can be subtle, because you need to make sure that object is in a state you can deal with in a reliable fashion. Some idiots like to add booleans to say "object's member function foo() in middle of call" and to clear that boolean and the end of the function call... this is just stupid, as it is error prone and makes for particular messy and painful cleanup code.

At the end of the day, what I am claiming that the idea of exception handling was to cut down on errors, increase reliability and have developers type less. But now with it, we get a whole messy can of worms to deal with because of its automatic nature. I can trust a much less experienced developer to correctly write "check return code" source than to write correct exception handling source, as the former is much easier to write, review and test. If a developer does not check their return codes, they are not average, they are incompetent.



Quote:

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


*cough* for Desktops, have you heard of Mac OS X? for a great deal number of devices and servers have you heard of BSD? and Windows "kernels" means different things: Win9x kernel (eww), NT kernel (many versions NT3.x, 4.x, 2000, XP, Vista)

and the action is not really in the desktops nowadays, the cutting edge is small devices and embedded devices, there you find many kernels, many of them custom for the device. One such example is Symbian, which should be burned from humanity as it is an abomination.


Quote:

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.


for exception handling, I fully recommend not to use it. To use error codes and check for them. Code is easier to review, write and test. Yes it does not look "clean", but it gets the job done reliably and with less work. I cannot be that off my wanker, as many compilers include optimization flags to turn exception handling off, unless one wants to argue that the compiler writes are nuts as well.

Quote:

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.


I will confess I don't actually know if MSVC compiler does not use exception handling, but for those sick enough, you can check. MSVC compiled code's exception handling comes to some degree from the OS (I leave you to use google for that) and with some debug tools one can see if CL.exe has the junk that says it is using the exception handling of Win32/64 systems.. admittedly they could use another implementation, but I would find that hard to believe. My point is that very complicated robust systems have been made, and work reliably, without exception handling.


kRogue, while I don't agree with a lot of what you said, I will say that I appreciate your persistence and your attempts to explain things. With that said:

Regarding Java, I'm not in love with finally, as I would've preferred a safer RAII mechanism. That being said, I don't blame exceptions for this. I think as time goes on, more OO languages will implement RAII or facsimiles. Java was extremely foolish not to have done so.

Regarding throwing exceptions into other threads, I won't argue that things can get more compicated... but if you're at the point where you need to do this, you're at the position of crossing a thread boundary, so it's usually (not always) simpler than sending a message and rethrowing at a random spot. I've particularly used this technique in an OpenMP or TBB parallelized for loop, where I simply store that the exception happened, cause that single thread to fail out gracefully. When the other threads finish, the exception state is checked, and then it rethrows. It's about 3 or 4 extra lines of code.

As far as SWIG, SWIG for Java certainly gives you a way to translate a C++ exception and rethrow it as a Java exception. This technique worked quite well, but does cause me to have to review which methods I'm exposing. As for Boost.Python, again, there's simple ways to register exception translators. From the C++ side, the Python C API makes it pretty easy to translate Python exceptions back into C++, as well. Again, I've done this before and it really wasn't that big a challenge.

I think exceptions should be a design decision, agreed. I am very against seeing more than one or two types of exceptions that can be propagated from a given method. So, at design time, I have a COMPort class (for example), it will generally be designed to only throw COMPortExceptions. That means that if something else fails internally, say an std::runtime_error, the exception is caught internally and translated to the expected exception with extra debugging information added. That's why I asked if you had worked on systems that were a mess, since systems that allow 50 different exceptions to potentially propagate from any given method are unmanagable. If that was the only option for exception handling, I'd agree with you.

As for checking return codes, I find it is common practice. Most people don't even really think about it. At best, the average checking for return codes turns into this:


if (!x.doSomething())
{
log("Some error happened");
}



I think the learning curve for exceptions is longer, but at the same time, when I've seen good exception handling code written, it has been at a higher quality than the error code versions.

Finally, you've mentioned the testing multiple times. Well written code should document under what conditions that an exception may be possible. It should be possible to reproduce the exception under the right test. On the contrary, I've worked in situations where I know of MANY errors which never would've been noticed if we were not using exceptions. Another advantage if your debugger can tell you when an exception is thrown, regardless of whether or not it is handled. Return codes suffer from the potential to be accidentally ignored, and there is very little assistance you can get if this happens. I'm of the mindset of wanting to have serious errors very visible.

Share this post


Link to post
Share on other sites
Quote:
Original post by kRogue

*cough* for Desktops, have you heard of Mac OS X? for a great deal number of devices and servers have you heard of BSD? and Windows "kernels" means different things: Win9x kernel (eww), NT kernel (many versions NT3.x, 4.x, 2000, XP, Vista)

and the action is not really in the desktops nowadays, the cutting edge is small devices and embedded devices, there you find many kernels, many of them custom for the device. One such example is Symbian, which should be burned from humanity as it is an abomination.


I suppose we'd have to define here first which advice can serve as generalization.

That which applies to cutting edge? Or that which applies to mainstream?

Formula 1/Nascar use a lot of cutting edge. Is that how cars should be maintained? Engines tuned so that they last 200km before they blow up?


Quote:
For exception handling, I fully recommend not to use it. To use error codes and check for them. Code is easier to review, write and test.


Error codes do not solve the resource management problem.

If anything, they require more work, and obfuscate the origin of problem if used improperly.

So not only does trouble-shooting need to deal with source of error, they must also take into account potentially incorrect use of error codes (it goes without saying, that every single programmer, from beginner to world-class pro has forgotten to check an error code at some point).

Not to mention a more important thing: In all memory managed languages error codes can be used. They're likely more efficient. Why aren't they, if they are superior?

And again, just because there are domains where compilers or OS don't cooperate too well, they are, despite some popularity, a transient, niche market. In markets built around constraint resources, there will be trade-offs.

But let's not forget that average cell-phone today has more computing power than hard-core gaming rigs did not more than 10 years ago.

If you're stuck with some broken compiler/incomplete platform/legacy or specific code, then you will need to work around those issues. If this means dropping $FEATURE, you will.

But it's not applicable to language, or paradigm. Exception safety is a concept that is very well defined in scope of C++. The problem itself goes waaay beyond that. The lessons that can be learned from handling these concepts may be implemented in different ways (error codes, transaction managers, external database, or even non-deterministic state if desired).

But at the same time, the concept of exception safety raises some more questsions. Why isn't STM catching up? It promises to be best thing since sliced bread, yet there's always one more problem to solve.

It really isn't as trivial as not using exceptions. Exceptions and error codes are both just mechanisms. You'll use whatever you can on your given platform. But original problem of state consistency remains.

What to do in case of an error. Not how.

This is the question to answer.

Share this post


Link to post
Share on other sites
Quote:

I suppose we'd have to define here first which advice can serve as generalization.

That which applies to cutting edge? Or that which applies to mainstream?

Formula 1/Nascar use a lot of cutting edge. Is that how cars should be maintained? Engines tuned so that they last 200km before they blow up?


Perhaps cutting edge is not the right phrase: places of growth, the biggest one being, now, is cell phones... and those environments are very tight. You minimize code size, minimize overhead... its a rough environment... but let give you an idea of how exception handling error reporting does not seem to work so well: got a new cell phone? bet it crashes much more than your old phone, reason: programming parardigms, for Nokia phones: old=ISA, C-based OO system, no exception handling, horrifying macros, tons of pointer casts but is rock solid, new phones: Symbian C++ based, exception handling style errors, much slower and buggy as hell ... in spite of the added type safety, virtual functions and frameworks, the fact is that most of the error come from the exception handling system, even development time for the same do-hicky is longer on Symbian... developers on average are not better or worse, the reason for the decline is the paradigm used for error handling is horrid. don't let me talk about WindowsMobile, that is awful (it uses error codes though), so I won't say using error codes guarantees good results, but it is more likely to be easier to do.

Quote:

Error codes do not solve the resource management problem.

If anything, they require more work, and obfuscate the origin of problem if used improperly.


I strongly disagree with the notion that error codes require more work. The argument against error codes is that you need to check them and propagate them, which on the surface looks like a lot more work than using exception handling since, on the surface, exception handling looks like it requires less typing. But here is the crux: on the surface. I will re-iterate my opinion again, writing exception safe code is much more difficult since an exception can be thrown on a function call, that means that you need to make sure you can handle the objects state at cleanup up/error recovery for each state the object is in before a function that can throw an exception. For complicated algorithms this can be really, really hard to guarantee. The advantage of error codes is that the error is better localized. Now often people like to do this: error codes for little problems, exceptions for big problems, that might be ok, but still someone has to catch the exception and restore the state.

and yes, screwing up error codes can royally obfuscate the origin of a given problem. This is where logging can help, my opinion is that if a called function returns an error code it should be logged and ones logging system should allow that a break point is placed at any log, this way one can see that call stack in the debugger. This is not hard to do and most large projects, even those that use exception handling to the extreme, do this.


Quote:

It really isn't as trivial as not using exceptions. Exceptions and error codes are both just mechanisms. You'll use whatever you can on your given platform. But original problem of state consistency remains.


One of my points is that exception handling makes it typically harder, since often the catcher is many levels away from where the problem occurred, I prefer the following pattern to handle when bad things happen:

check error code
if error happens, deal with error locally, restore local state (if possible) and return an error code.

for a large number of situations restoring local state is much easier, and this mentality works as the stack is unwound, restoring the local state at each function call.. with exception handling you have to make sure that the state is reliable when an exception is thrown, and that means all the functions below need to have that for each function they call, they need to guarantee that the state is manageable above, but the cleanup at the top could possible have all sorts of cases it has to deal with... one can argue that careful use of local objects will get things right for us, for example, do this:

localObject worker;
worker.DoSomething(parameters)

instead of this:
DoSomething(paremeters)

with the idea being the the local object's deconstructor will do the local cleanup, of a botched DoSomething(). but now we have to make another class, and for the code reviewer or if a new maintainer comes, makes it that much harder to see what is going on.... the error code mechanism helps keep things local rather than having to run around in the source to see what each little piece does...

all I am trying to argue is that the gains of using exception handling quickly evaporate when projects get more complicated and their scope grows... error codes keep things local and easy to manage.... the easiest thing to do is for one's institution's coding standards to state: "Must check return codes", review the code, and if a return code is not checked, that place fails in the review and is flagged as requiring work.

though,
Quote:

What to do in case of an error. Not how.


is a much harder problem, sh*t hits the fan, now what can we do to recover, even today the vast majority of the time is to either just exit, or abort the request, in a few cases the program can carry on (easy examples like textures not loading) but what happens in things that are practically fatal? typically we abort, guess that is why it is called fatal error.

Quote:

I think the learning curve for exceptions is longer, but at the same time, when I've seen good exception handling code written, it has been at a higher quality than the error code versions.


naturally, and I will bet that the time to write the error code version was much less (or needed a far less capable developer) than the exception handling code. More often that not both. Would you trust a junior developer to write exception safe code? I would not, but I trust them to write status checking code.


Quote:

Finally, you've mentioned the testing multiple times. Well written code should document under what conditions that an exception may be possible.


and that is a major stinker! if exceptions are used by a supporting 3rd party library, then it is even harder... how many have you seen a less than competent colleague throw exceptions when they should not, but they did because it was easier for them than to deal with the error, since they just passed it on rather than dealing with it in some reasonable fashion. Or for that matter, your spec says "Must use this library" which has issues on documentation. often developers are viewed as quasi-interchangeable parts, so what happens when you are debugging someone else's code? the rules for state consistency are vague, what functions throw is not well documented, etc... with status code checking, the code gets a semblance of self documentation.


Quote:

I think exceptions should be a design decision, agreed. I am very against seeing more than one or two types of exceptions that can be propagated from a given method. So, at design time, I have a COMPort class (for example), it will generally be designed to only throw COMPortExceptions. That means that if something else fails internally, say an std::runtime_error, the exception is caught internally and translated to the expected exception with extra debugging information added. That's why I asked if you had worked on systems that were a mess, since systems that allow 50 different exceptions to potentially propagate from any given method are unmanagable. If that was the only option for exception handling, I'd agree with you.


But what happens internally, a catch block is made, and anything caught is rethrown as a COMPortException. This is the same thing as propagating error codes, but with the very high overhead of exception handling. Worse, you have to do this for every COMPort method. It kind of defeats the purpose of exception handling some: throw if you can't recover, now you are re-throwing...


because I am grumpy (still) how many of you allow for exception when the customer is using the program? does your program really recover? Do you use exception handling really as a funky logging system? 99/100, assert() is what is meant and not throw.

Share this post


Link to post
Share on other sites

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

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

Create an account or sign in to comment

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

Create an account

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

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this