views:

1630

answers:

13

EH seems to be the current standard, and by searching the web, I can not find any novel ideas or methods that try to improve or replace it (well, some variations exist, but nothing novel).

Though most people seem to ignore it or just accept it, EH has some huge drawbacks: exceptions are invisible to the code and it creates many, many possible exit points. Joel on software wrote an article about it. The comparison to goto fits perfect, it made me think again about EH.

I try to avoid EH and just use return values, callbacks or whatever fits the purpose. But when you have to write reliable code, you just can't ignore EH these days: It starts with the new, which may throw an exception, instead of just returning 0 (like in the old days). This makes about any line of C++ code vulnerable to an exception. And then more places in the C++ foundational code throw exceptions... std lib does it, and so on.

This feels like walking on shaky grounds.. So, now we are forced to take care about exceptions!

But its hard, its really hard. You have to learn to write exception safe code, and even if you have some experience with it, it will still be required to double check any single line of code to be safe! Or you start to put try/catch blocks everywhere, which clutters the code until it reaches a state of unreadability.

EH replaced the old clean deterministical approach (return values..), which had just a few but understandable and easily solveable drawbacks with an approach that creates many possible exit points in your code, and if you start writing code that catches exceptions (what you are forced to do at some point), then it even creates a multitude of paths through your code (code in the catch blocks, think about a server program where you need logging facilities other than std::cerr ..). EH has advantages, but that's not the point.

My actual questions:

  • Do you really write exception safe code?
  • Are you sure your last "production ready" code is exception safe?
  • Can you even be sure, that it is?
  • Do you know and/or actually use alternatives that work?

Pro EH arguments like "but its just in exceptional cases" or "usually you will just exit the application anyway" or "do not use exceptions for anything other than exceptional cases" or "do not use them to change program flow" will be ignored or even down-voted ;-)

EDIT: replaced "SEH" with "EH" (exception handling) to avoid confusion.

EDIT 2: Also, please don't write answers like "it worked for XX number of years...". This doesn't help, and its not the point. Newton's laws also worked and are still valid, though we know that they may not be the right thing.

EDIT 3: I'd like to add another question: Who invented the "throw/try/catch exception handling" kind of error handling, and when and for which language? Any sources known?

+11  A: 

Some of us have been using exception for over 20 years. PL/I has them, for example. The premise that they are a new and dangerous technology seems questionable to me.

bmargulies
Please don't get me wrong, I am (or am trying to) questioning EH. And especially C++ EH. And I am searching for alternatives. Maybe I have to accept it (and I will if its the only way), but I think there could be better alternatives. Its not that I think that the concept is new, but yes, I think it _can_ be more dangerous than explicit error handling with return codes...
frunsi
If you don't like it, don't use it. Put try blocks around things you need to call that can throw, and reinvent ye-olde-error-code, and suffer with the problems it has, which in some cases are tolerable.
bmargulies
Ok, perfect, I will just use EH and error-codes, and live with it. I'm a nitwit, I should have come to that solution on my own! ;)
frunsi
+11  A: 
  • Do you really write exception safe code?

Well, I certainly intend to.

  • Are you sure your last "production ready" code is exception safe?

I'm sure that my 24/7 servers built using exceptions run 24/7 and don't leak memory.

  • Can you even be sure, that it is?

It's very difficult to be sure that any code is correct. Typically, one can only go by results

  • Do you know and/or actually use alternatives that work?

No. Using exceptions is cleaner and easier than any of the alternatives I've used over the last 30 years in programming.

anon
This answer has no value.
Matt Joiner
+5  A: 

Leaving aside the confusion between SEH and C++ exceptions, you need to be aware that exceptions can be thrown at any time, and write your code with that in mind. The need for exception-safety is largely what drives the use of RAII, smart pointers, and other modern C++ techniques.

If you follow the well-established patterns, writing exception-safe code is not particularly hard, and in fact it's easier than writing code that handles error returns properly in all cases.

Mark Bessey
+1  A: 

I really like working with Eclipse and Java though (new to Java), because it throws errors in the editor if you are missing an EH handler. That makes things a LOT harder to forget to handle an exception...

Plus, with the IDE tools, it adds the try / catch block or another catch block automatically.

Crowe T. Robot
That is the difference between checked(Java) and unchecked(c++) exceptions (Java has a few of those too). The advantage of checked exceptions is what you wrote, but there it has its own disadvantages. Google for the difference approaches and the different problems they have.
David Rodríguez - dribeas
A: 

Some of us prefer languages like Java which force us to declare all the exceptions thrown by methods, instead of making them invisible as in C++ and C#.

When done properly, exceptions are superior to error return codes, if for no other reason than you don't have to propagate failures up the call chain manually.

That being said, low-level API library programming should probably avoid exception handling, and stick to error return codes.

It's been my experience that it's difficult to write clean exception handling code in C++. I end up using new(nothrow) a lot.

Loadmaster
And you're avoiding most of the standard library as well? Using `new(std::nothrow)` is not enough. By the way, it's easier to write exception-safe code in C++ than in Java: http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
avakar
The Java checked exception usability is highly exagerated. In fact, non-Java languages, they are NOT considered a success. This is why the "throw" statement in C++ is now considered obsolete, and C# never seriously considered implementing them (this was a design choice). For Java, the following document could be enlightning: http://googletesting.blogspot.com/2009/09/checked-exceptions-i-love-you-but-you.html
paercebal
It's my experience that writing exception-safe code in C++ is not that hard, and that it tends to lead to cleaner code in general. Of course, you do have to learn to do it.
David Thornley
+8  A: 

Writing exception-safe code in C++ is not so much about using lots of try { } catch { } blocks. It's about documenting what kind of guarantees your code provides.

I recommend reading Herb Sutter's Guru Of The Week series, in particular installments 59, 60 and 61.

To summarize, there are three levels of exception safety you can provide:

  • Basic: When your code throws an exception, your code does not leak resources, and objects remain destructible.
  • Strong: When your code throws an exception, it leaves the state of the application unchanged.
  • No throw: Your code never throws exceptions.

Personally, I discovered these articles quite late, so much of my C++ code is definitely not exception-safe.

Joh
His book "Exceptional C++" is also a good read. But I still try to question the concept of EH...
frunsi
+1 OP conflates handling exceptions (catching them) with exception safety (usually more about RAII)
jk
+5  A: 

First of all (as Neil stated), SEH is Microsoft's Structured Exception Handling. It is similar to but not identical to exception processing in C++. In fact, you have to enable C++ Exception Handling if you want it in Visual Studio - the default behavior does not guarantee that local objects are destroyed in all cases! In either case, Exception Handling is not really harder it is just different.

Now for your actual questions.

Do you really write exception safe code?

Yes. I strive for exception safe code in all cases. I evangelize using RAII techniques for scoped access to resources (e.g., boost::shared_ptr for memory, boost::lock_guard for locking). In general, consistent usage of RAII and scope guarding techniques will make exception safe code much easier to write. The trick is to learn what exists and how to apply it.

Are you sure your last "production ready" code is exception safe?

No. It is as safe as it is. I can say that I haven't seen a process fault due to an exception in several years of 24/7 activity. I don't expect perfect code, just well-written code. In addition to providing exception safety, the techniques above guarantee correctness in a way that is near impossible to achieve with try/catch blocks. If you are catching everything in your top control scope (thread, process, etc.), then you can be sure that you will continue to run in the face of exceptions (most of the time). The same techniques will also help you continue to run correctly in the face of exceptions without try/catch blocks everywhere.

Can you even be sure that it is?

Yes. You can be sure by a thorough code audit but no one really does that do they? Regular code reviews and careful developers go a long way to getting there though.

Do you know and/or actually use alternatives that work?

I have tried a few variations over the years such as encoding states in the upper bits (ala HRESULTs) or that horrible setjmp() ... longjmp() hack. Both of these break down in practice though in completely different ways.


In the end, if you get into the habit of applying a few techniques and carefully thinking about where you can actually do something in response to an exception, you will end up with very readable code that is exception safe. You can sum this up by following these rules:

  • You only want to see try/catch when you can do something about a specific exception
  • You almost never want to see a raw new or delete in code
  • Eschew std::sprintf, snprintf, and arrays in general - use std::ostringstream for formatting and replace arrays with std::vector and std::string
  • When in doubt, look for functionality in Boost or STL before rolling your own

I can only recommend that you learn how to use exceptions properly and forget about result codes if you plan on writing in C++. If you want to avoid exceptions, you might want to consider writing in another language that either does not have them or makes them safe. If you want to really learn how to fully utilize C++, read a few books from Herb Sutter, Nicolai Josuttis, and Scott Meyers.

D.Shawley
+1  A: 

It is not possible to write exception-safe code under the assumption that "any line can throw". The design of exception-safe code relies critically on certain contracts/guarantees that you are supposed to expect, observe, follow and implement in your code. It is absolutely necessary to have code that is guaranteed to never throw. There are other kinds of exception guarantees out there.

In other words, creating exception-safe code is to a large degree a matter of program design not just a matter of plain coding.

AndreyT
+41  A: 

Your question makes an assertion, that it "Writing exception safe code is very hard". I will answer your questions first, and then, answer the hidden question behind them.

Answering questions

Do you really write exception safe code?

Of course I do.

This is the reason Java lost a lot of its appeal to me as a C++ programmer (lack of RAII semantics), but I am digressing: This is a C++ question.

It is in fact necessary when you need to work with STL or Boost code. For example, C++ threads (boost::thread or std::thread) will throw an exception to exit gracefully.

Are you sure your last "production ready" code is exception safe?

Can you even be sure, that it is?

Writting exception-safe code, is like writting bug-free code.

You can't be 100% sure your code is exception safe. But then, you strive for it, using well known patterns, and avoiding well known anti-patterns.

Do you know and/or actually use alternatives that work?

There are no viable alternatives in C++ (i.e. you'll need to revert back to C, and avoid C++ libraries, as well as external surprises like Windows SEH).

Writing exception safe code

To write exception safe code, you must know first what level of exception safety each instruction you write is.

For example, a new can throw an exception, but assigning a built-in (e.g. an int, or a pointer) won't fail. A swap will never fail (don't even write a throwing swap), a std::list::push_back can throw...

Exception guarantee

The first thing to understand is that you must be able to evaluate the exception guarantee offered by all of your functions:

  1. none : Your code should never offer that. This code will leak everything, and break down at the very first exception thrown.
  2. basic : This is the guarantee you must at the very least offer, that is, if an exception is thrown, no resources is leaked, and all objects are still whole
  3. strong : The processing will either succeed, or throw an exception, but if it throws, then the data will be in the same state as if the processing had not started at all (this gives a transactional power to C++)
  4. nothrow/nofail : The processing will succeed.

Example of code

The following code seems like correct C++, but in truth, offers the "none" guarantee, and thus, it is not correct:

void doSomething(T & t)
{
   t.integer += 1 ;                 // 1. nothrow/nofail
   X * x = new X() ;                // 2. basic : can throw with new and X constructor
   t.list.push_back(x) ;            // 3. strong : can throw
   x.doSomethingThatCanThrow() ;    // 4. basic : can throw
}

I write all my code with this kind of analysis in mind.

The lowest guarantee offered is basic, but then, the ordering of each instruction makes the whole function "none", because if 3. throws, x will leak.

The first thing to do would be to make the function "basic", that is putting x in a smart pointer until it is safely owned by the list:

void doSomething(T & t)
{
   t.integer += 1 ;                 // 1.  nothrow/nofail
   std::auto_ptr<X> x(new X()) ;    // 2.  basic : can throw with new and X constructor
   X * px = x.get() ;               // 2'. nothrow/nofail
   t.list.push_back(px) ;           // 3.  strong : can throw
   x.release() ;                    // 3'. nothrow/nofail
   x.doSomethingThatCanThrow() ;    // 4.  basic : can throw
}

Now, our code offers a "basic" guarantee. Nothing will leak, and all objects will be in a correct state. But we could offer more, that is, the strong guarantee. This is were it can become costly, and this is why not all C++ code is strong. Let's try it:

void doSomething(T & t)
{
   std::auto_ptr<X> x(new X()) ;    // 1. basic : can throw with new and X constructor
   X * px = x.get() ;               // 2. nothrow/nofail
   x.doSomethingThatCanThrow() ;    // 3. basic : can throw

   T t2(t) ;                        // 4. strong : can throw with T copy-constructor
   t2.list.push_back(px) ;          // 5. strong : can throw
   x.release() ;                    // 6. nothrow/nofail
   t2.integer += 1 ;                // 7. nothrow/nofail

   t.swap(t2) ;                     // 8. nothrow/nofail
}

We re-ordered the operations, first creating and setting X to its right value. If any operation fails, then t is not modified, so, operation 1 to 3 can be considered "strong": If something throws, t is not modified, and X will not leak because it's owned by the smart pointer.

Then, we do a copy t2 of t, and work on this copy from operation 4 to 7. If something throws, t2 is modified, but then, t is still the original. We still offer the strong guarantee.

Then, we swap t and t2. Swap operations should be nothrow in C++, so lets hope the swap you wrote for T is nothrow (if it isn't, rewrite it so it is nothrow).

So, if we reach the end of the function, everything succeeded (No need of a return type) and t has its excepted value. If it fails, then t has still its original value.

Now, offering the strong guarantee could be quite costly, so don't strive to offer the strong guarantee to all your code, but if you can do it without a cost (and C++ inlining and other optimization could make all the code above costless), then do it. The function user will thank you for it.

Conclusion

It takes some habit to write exception-safe code. You'll need to evaluate the guarantee offered by each instruction you'll use, and then, you'll need to evaluate the guarantee offered by a list of instructions.

Of course, the C++ compiler won't back up the guarantee (in my code, I offer the guarantee as a @warning doxygen tag), which is kinda sad, but it should not stop you from trying to write exception safe code.

Normal failure vs. bug

How can a programmer guarantee that a nofail function will always succeed? After all, the function could have a bug.

This is true. The exception guarantees are supposed to be offered by bug-free code. But then, in any language, calling a function supposes the function is bug-free. No sane code protects itself against the possibility of it having a bug. Write code the best you can, and then, offers the guarantee with the supposition is bug free. And if there is a bug, correct it.

Exceptions are for exceptional processing failure, not for code bugs.

Last words

Now, the question is "Is this worth it ?".

Of course it is. Having a "nothrow/nofail" function knowing that the function won't fail is a great boon. The same can be said for "strong" function, which enable you to write code with transactional semantics, like databases, with commit/rollback features, the commit being normal execution of the code, throwing exceptions being the rollback.

Then, the "basic" is the very least guarantee you should offer. C++ is a very strong language there, with its scopes, enabling you to avoid any resource leaks (something a garbage collector would find it difficult to offer for database, connection or file handles).

So, as far as I see it, it is worth it.

Edit: About non-throwing swap

nobar made a comment that, I believe, is quite relevant, because it is part of "how do you write exception safe code":

  • [me] A swap will never fail (don't even write a throwing swap)
  • [nobar] This is a good recommendation for custom-written swap() functions. It should be noted, however, that std::swap() can fail based on the operations that it uses internally

the default std::swap will make copies and assignments, which, for some objects, can throw. Thus, the default swap could throw, either used for your classes, or even for STL classes. As far as the C++ standard is concerned, the swap operation for vector, deque, and list won't throw, whereas it could for map if the comparison functor can throw on copy construction (See The C++ Programming Language, Special Edition, appendix E, E.4.3.Swap).

Looking at Visual C++ 2008 implementation of the vector's swap, the vector's swap won't throw if the two vectors have the same allocator (i.e., the normal case), but will make copies if they have different allocators. And thus, I assume it could throw in this last case.

So, the original text still holds: Don't even write a throwing swap, but nobar's comment must be remembered: Be sure the objects you're swaping have a non-throwing swap.

paercebal
Thanks a lot! I still hoped for something different. But I accept your answer for sticking to C++ and for your detailed explanation. You even disproved my "This makes about any line of C++ code vulnerable to an exception"-offense. This line by line analysis makes sense after all... I have to think about it.
frunsi
Beautiful answer.
ChaosPandion
Just writing down an idea, before I'll forget it: wouldn't it then be nice and comfortable to _annotate_ each single line of code with 1) the guarantee level and 2) a term of "what" to do if a given exception occurs in that given line? Maybe like a second **layer** of code, that will be executed whenever the program leaves the "usual code path". This would provide a complete separation of code and error handling, and in practice one could write code, and write error handling in a second step (without intermingling them too much).
frunsi
@frunsi: Sounds like a good learning exercise, but not practical in general. Once you've learned exception safety (and the above is a very good answer), you start seeing safe and unsafe constructs for yourself.
David Thornley
... this separation of code and error handling is somehow similar to what EH promises (besides other things, I can imagine that the initial IDEA of it was to _remove_ the burden of error handling from application code). Maybe the removal should be more explicit - line-by-line in a second code layer or something similar. Well, forget text editors then, but the idea is interesting...
frunsi
@David Thornley: I know exception safety (at least the basics and some practice applying it). But I still do not _like_ it.
frunsi
.. one more note: when exception handling was meant as a replacement for error-return-codes, then it _was_ meant to (plainly speaking) remove error-handling code from application code. So, it was (also plainly speaking) invented to _separate_ error-handling code from application code. So, maybe the _current_ concept of EH works and is matured, but there could be completely different solutions (that are still to be developed).
frunsi
@frunsi: Thanks for the comment. The try/catch block already separate the normal code and the error handling code, but I don't believe it should be more separated: error handling code must sometimes access normal code (i.e. local variables) to retrieve useful data (perhaps to try again the processing, or try it differently, etc.). Now, of course you're right: When I wrote "it's the only viable for C++", it's because it is the only viable for current C++ language specification. Research could well lead to other error handling means.
paercebal
@frunsi: Exception safety has funny effects. For example, when you have a function calling only nofail/nothrow code, and when you realize that as a whole, the function is nofail/nothrow, too, there is a little moment of "shining" that somehow makes your day brighter. The same can be said for strong functions. When you have a function that seems to do a lot, and you can offer a strong/commit/rollback guarantee, you see your code from a new viewpoint. I started wrapping SQLite3 C code into C++ code, and offering strong/nothrow guarantees to transactional concept was both fun and rewarding.
paercebal
"A swap will never fail". This is a good recommendation for custom-written swap() functions. It should be noted, however, that std::swap() can fail based on the operations that it uses internally.
nobar
@nobar: +1 for the comment. I'll edit my answer to add your contribution.
paercebal
+1  A: 

I try my darned best to write exception-safe code, yes.

That means I take care to keep an eye on which lines can throw. Not everyone can, and it is critically important to keep that in mind. The key is really to think about, and design your code to satisfy, the exception guarantees defined in the standard.

Can this operation be written to provide the strong exception guarantee? Do I have to settle for the basic one? Which lines may throw exceptions, and how can I ensure that if they do, they don't corrupt the object?

jalf
A: 

A lot (I would even say most) people do.

What's really important about exceptions, is that if you don't write any handling code - the result is perfectly safe and well-behaved. Too eager to panic, but safe.

You need to actively make mistakes in handlers to get something unsafe, and only catch(...){} will compare to ignoring error code.

ima
Not true. It's very easy to write code that is not exception-safe. For example: `f = new foo(); f->doSomething(); delete f;` If the doSomething method throws an exception, then you have a memory leak.
Kristopher Johnson
It wouldn't matter when you program terminates, right? To continue execution, you'll have to _actively_ swallow exceptions.Well, there are some specific cases where termination without clean-up is still unacceptable, but such situations needs special care in any programming language and style.
ima
You just can't ignore exceptions (and not write handling code), neither in C++ nor in managed code. It will be unsafe, and it will not well-behave. Except for maybe some toy code.
frunsi
Did I say you can? Now read again what I wrote and try to understand.
ima
With exceptions, unsafe code results from you making mistakes in handling code. With error codes, unsafe code results from you not writing handling code. The difference is huge.
ima
You wrote "[..] if you don't write any handling code - the result is perfectly safe and well-behaved [..]". So, this sounds like "you _can_ write perfectly safe and well-behaved code even if you ignore exceptions". not handling exceptions = ignoring them. Of course its similar situation with error codes. But that's not the point. You just _wrote_ that ignoring exceptions results is safe and well-behaved code ;-) But yes, I understand your intention.
frunsi
If you ignore exceptions in application code, the program may still NOT behave well when external ressources are involved. True, the OS cares about closing file handles, locks, sockets and so on. But not everything is handled, e.g. it may leave unnecessary files or damage files while writing to them and so on. If you ignore exceptions, you have a problem in Java, in C++ you can work with RAII (but when you use RAII technique, you most probably use them because you _care_ about exceptions)...
frunsi
Please, don't twist my words. I wrote "if you don't write any handling code " - and I meant exactly that. To ignore exceptions, you need to write code.
ima
A: 
  • Do you really write exception safe code? [There's no such thing. Exceptions are a paper shield to errors unless you have a managed environment. This applies to first three questions.]

  • Do you know and/or actually use alternatives that work? [Alternative to what? The problem here is people don't separate actual errors from normal program operation. If it's normal program operation (ie a file not found), it's not really error handling. If it's an actual error, there is no way to 'handle' it or it's not an actual error. Your goal here is to find out what went wrong and either stop the spreadsheet and log an error, restart the driver to your toaster, or just pray that the jetfighter can continue flying even when it's software is buggy and hope for the best.]

Charles Eli Cheese
A: 

EH is good, generally. But C++'s implementation is not very friendly as it's really hard to tell how good your exception catching coverage is. Java for instance makes this easy, the compiler will tend to fail if you don't handle possible exceptions .

John