tags:

views:

566

answers:

11

I'm having a discussion about which way to go in a new C++ project. I favor exceptions over return codes (for exceptional cases only) for the following reasons -

  1. Constructors can't give a return code
  2. Decouples the failure path (which should be very rare) from the logical code which is cleaner
  3. Faster in the non-exceptional case (no checking if/else hundreds of thousands of times)
  4. If someone screws up the return code settings (forgets to return FAIL) it can take a very long time to track down.
  5. Better information from the message contained in the error. (It was pointed out to me that a return enum could do the same for error codes)
  6. From Jared Par Impossible to ignore without code to specifically designed to handle it

These are the points I've come up with from thinking about it and from google searches. I must admit to being predisposed to exceptions having worked in C# for the past couple of years. Please post further reasons for using exceptions over return codes. For those who prefer return codes, I would also be willing to listen to your reasoning. Thanks

+6  A: 

IMHO, the #1 reason to prefer exceptions over return codes is you can't silently ignore an exception. Doing so requires at least a minimum amount of extra code.

JaredPar
I can certainly ignore an exception, just don't handle it. Of course, if QA misses the particular exceptional case then my program is sure to crash when it gets released, whereas with return codes the program may continue to work properly. It may not, but with the exception it is certain to crash if someone doesn't realize they need to try/catch around a function call.
tloach
"whereas with return codes the program may continue to work properly" - you miss the other case, which is actually most typical with return-value error handling: the program continues to work, but it works _improperly_. At best, in that scenario, you get a weird crash sometime later, which is extremely hard to debug because all that you see is that some data invariants are broken, with no idea as to who did them. The worst-case scenario is that your program doesn't crash at all, but it doesn't work correctly, and silently corrupts data (in-memory, which can be saved to disk eventually).
Pavel Minaev
@tloach if you don't handle it it's not ignored. It will instead wreak havoc on your program typically in the form of a crash. It's very easy though to ignore a failed HRESULT
JaredPar
@JaredPar: But stack unwinds don't "wreak havoc". They perform as orderly a shutdown as can be hoped for, given the circumstances.
DrPizza
@DrPizza, the unwind is only orderly if you're catching the exception and have written code which is designed to be exception safe. This goes back to not being able to ignore it silently, it requires work
JaredPar
The unwind is orderly just so long as you have a catch(...) around main (according to the spec), or, in practice, regardless of what you do (technically a stack unwind requires there to be at least one matching handler; in reality, it unwinds all the way even if there isn't a handler, and just crashes the program once it's finished unwinding). But as long as the stack unwind occurs, it's orderly.
DrPizza
+19  A: 

I think this article sums it up.

Arguments for Using Exceptions

  1. Exceptions separate error-handling code from the normal program flow and thus make the code more readable, robust and extensible.
  2. Throwing an exception is the only clean way to report an error from a constructor.
  3. Exceptions are hard to ignore, unlike error codes.
  4. Exceptions are easily propagated from deeply nested functions.
  5. Exceptions can be, and often are, user defined types that carry much more information than an error code.
  6. Exception objects are matched to the handlers by using the type system.

Arguments against Using Exceptions

  1. Exceptions break code structure by creating multiple invisible exit points that make code hard to read and inspect.
  2. Exceptions easily lead to resource leaks, especially in a language that has no built-in garbage collector and finally blocks.
  3. Learning to write exception safe code is hard.
  4. Exceptions are expensive and break the promise to pay only for what we use.
  5. Exceptions are hard to introduce to legacy code.
  6. Exceptions are easily abused for performing tasks that belong to normal program flow.
Nikola Smiljanić
Usually, it's kind to put a summary in your SO answer so a passerby can get an idea of what the article says.
GMan
Thanks for the tip, fixed :)
Nikola Smiljanić
Not least because the page you linked to might be taken down tomorrow. Then your answer would be worthless if you didn't put a summary here
jalf
No problem, now you get an up-vote :P It also helps flesh your answer out so it doesn't look so small.
GMan
"Exceptions are expensive and break the promise to pay only for what we use." - *throwing* exceptions is expensive, wrapping code that does not cause an exception in a 'try' block is not (although if code size is a premium, you have a valid point). So you really only pay a very small amount for what you may not use.
Ed Swangren
I've done some performance analysis, and if exceptions are used for exceptional cases only then having try blocks inside loops have almost no performance impact on release-built VC++ and gcc -O2 code.
Marius
In regards to the arguments against:1: invalid. Throwing an exception is not invisible. Allowing an exception to pass from called code, however, can be.2. RAII.3. Don't throw from destructors. See response to #2.4. Catching exceptions is expensive. Throwing, however, generally is not.5. Explain?6. Yes, and developers that introduce such exceptions as control logic should be beaten.
Nathan Ernst
+2  A: 

Faster in the non-exceptional case (no checking if/else hundreds of thousands of times)

In the non-exceptional case, it's a single comparison to determine that E_SUCCESS was returned.

If someone screws up the return code settings (forgets to return FAIL) it can take a very long time to track down.

If someone fails to check the exceptions, it can be difficult to notice until you actually get an exception thrown there. If you're dealing with error codes, you know just by looking at it whether they're checking for them or not.

Anon.
What if you have a tight loop that performs an operation that can fail? Each time it executes you have to make sure it succeeded, or otherwise exit. For my second point, I meant they didn't change the code to FAIL at an appropriate point, so E_SUCCESS is returned accidentally in the case of failure.
Steve
We are talking about a single integer comparison here. You're comparing what is already in eax, probably against zero. It's most likely a single machine instruction, which is insignificant compared to even the cost of the function call. There are reasons to choose one over the other, but normal-case performance isn't one of them.
Anon.
Note that using the return value for an error code means that _actual_ return value has to be passed by other means, and most typically it's a reference or a pointer type (as in COM `HRESULT` pattern). That definitely inhibits optimizations, as not only it's an extra argument to pass, but the variable that will store the result can no longer be a register variable (unless the compiler will fully inline the body of the called function, including the indirection).
Pavel Minaev
Pavel Minaev
-1: The "single comparison" has to be propagated across a large part of the call stack in most cases, since proper exception handling (IMO this is true for error handling in general) is mostly done far away from the error location.
Fabio Fracassi
+4  A: 

The best case I've heard for preferring return codes over exceptions is simply this:

  1. Writing exception-safe code is hard [in C++].

With a great deal of recent experience in C# myself, I can empathize with your desire to use exceptions, but unfortunately C++ isn't C#, and a lot of things that we can get away with in C# can be ultimately deadly in C++.

A good summation of the case for and against can be found in Google's style guidelines. In short:

Pros:

  • Exceptions allow higher levels of an application to decide how to handle "can't happen" failures in deeply nested functions, without the obscuring and error-prone bookkeeping of error codes.
  • Exceptions are used by most other modern languages. Using them in C++ would make it more consistent with Python, Java, and the C++ that others are familiar with.
  • Some third-party C++ libraries use exceptions, and turning them off internally makes it harder to integrate with those libraries.
  • Exceptions are the only way for a constructor to fail. We can simulate this with a factory function or an Init() method, but these require heap allocation or a new "invalid" state, respectively.
  • Exceptions are really handy in testing frameworks.

Cons:

  • When you add a throw statement to an existing function, you must examine all of its transitive callers. Either they must make at least the basic exception safety guarantee, or they must never catch the exception and be happy with the program terminating as a result. For instance, if f() calls g() calls h(), and h throws an exception that f catches, g has to be careful or it may not clean up properly.
  • More generally, exceptions make the control flow of programs difficult to evaluate by looking at code: functions may return in places you don't expect. This results maintainability and debugging difficulties. You can minimize this cost via some rules on how and where exceptions can be used, but at the cost of more that a developer needs to know and understand.
  • Exception safety requires both RAII and different coding practices. Lots of supporting machinery is needed to make writing correct exception-safe code easy. Further, to avoid requiring readers to understand the entire call graph, exception-safe code must isolate logic that writes to persistent state into a "commit" phase. This will have both benefits and costs (perhaps where you're forced to obfuscate code to isolate the commit). Allowing exceptions would force us to always pay those costs even when they're not worth it.
  • Turning on exceptions adds data to each binary produced, increasing compile time (probably slightly) and possibly increasing address space pressure.
  • The availability of exceptions may encourage developers to throw them when they are not appropriate or recover from them when it's not safe to do so. For example, invalid user input should not cause exceptions to be thrown. We would need to make the style guide even longer to document these restrictions!

I suggest reading through and understanding the pros and cons, then making a decision for your own project based on these. You don't have the same software that google has, so what makes sense for them may not make sense for you (which is why I omitted their conclusion).

Greg D
Yeah, I've read that and thought it was very interesting. I need to think about their points.
Steve
It may be true that writing exception-safe code is hard, but writing correct code that relies on checking all return codes is no easier.
Kristopher Johnson
@Kristopher: I disagree for more or less the reasons I've already quoted in my answer. (The third bullet in the "Cons" section.)
Greg D
You missed on third bullet in Pros: the C++ standard library uses exceptions for lots of things. Turn exceptions off, and (a) you're not running standard C++ any more, and (b) the C++ library becomes unreliable.
David Thornley
@David: A fair point. I suggest you mention it to whomever maintains this list at google. :)
Greg D
One big reason Google doesn't use them is because Google doesn't use them. :) Basically, they're stuck. From the same coding standard document: "Because we'd like to use our open-source projects at Google and it's difficult to do so if those projects use exceptions, we need to advise against exceptions in Google open-source projects as well. Things would probably be different if we had to do it all over again from scratch."
Joe
@Joe: Yup. Again, the legacy code issue. Fortunately for the questioner, it's a new C++ project.
David Thornley
+1  A: 

One of the things I like about C++ is that it's very easy to think how the higher-level features might be implemented in terms of C features (which are easy to understand in terms of assembly). Exceptions for C++ break this mold. To get this level of understanding I have to do a lot. Just read this and you'll spend a lot of time scratching your head before you understand it.

Furthermore, exceptions require you to have good discipline making your code exception safe, and resource leak free. This means using RAII for anything that holds a resource ..

Additionally, exceptions have been shown when I have measured them to be a many, many orders of magnitude slower compared to a simple return code.

Well then they say you should only throw in exceptional circumstances, but how do you communicate the non-exceptional, expected, often-occuring errors. Well return codes of course! :)

I don't see how the benefits are worth it.

Doug T.
Additionally, exceptions have been shown when I have measured them to be a many, many orders of magnitude slower compared to a simple return code - of course throwing the exception is orders of magnitudes slower. I don't plan on throwing often, only when something exceptional happens. Otherwise, excellent points
Steve
The article you've linked to explains exception handling in MSVC, which uses Win32 SEH for design reasons. However, it is perfectly possible and straightforward to implement C++ exceptions in top of `setjmp` and `longjmp`, and, indeed, MinGW did just that for a long time. As for performance - exceptions are slower _if you throw them_. They can in fact be faster on success.
Pavel Minaev
good point on faster when you don't throw them, Pavel
Doug T.
+2  A: 

It is very hard to write exception safe code. A completely contrived example is :-

void Class::Method()
{ 
  i++;
  SomeCallThatMightThrow();
  j++;
}

Replace i++ and j++ with any two variables, resources, states that must remain synchronous. Garbage collection saved us from having to remember to pair our new's and deletes. Ironically, old fashioned explicit return code testing saves us from having to carefully analyse every function that might throw exceptions to check that they havn't screwed with the post-conditions.

Chris Becke
The problem I have with a statement that "writing exception safe code is hard" is that writing correct code checking return values is not any easier, because _you end up doing exact same thing the compiler does for you with exceptions_. Your contrived example looks somewhat deceptive, but consider if there were several interleaved function calls and variable increments. Now you need to do different kinds of cleanup depending on whether only the first call failed, or the first two calls failed... etc.
Pavel Minaev
Pavel Minaev
For the case you mention, the usual solution is Scope Guard idiom.
Nemanja Trifunovic
Yes, but scope guards (and any other idiom which scales for return-value error handling) are also exception safe!
Pavel Minaev
My comment was related to Chris' code snippet :)
Nemanja Trifunovic
+4  A: 

Use what makes sense. I think both have a place. There are situations where error codes are nearly impossible to use (returning failure from a constructor, for example)

Other times, error codes are just more convenient. They're easier to deal with in cases where you expect them to happen. Exceptions are for exceptional errors - the ones that aren't supposed to happen, but might do so once in a blue moon. Error codes are a lot more convenient for errors that are expected to happen regularly, and can be handled locally. Exceptions are most useful in cases where the error must be handled further up the call stack.

Also, exceptions aren't necessarily faster in the non-exceptional case. Often, they require extra exception handling code in function prolog and epilogs which has to be executed every time the function is called, whether or not it throws an exception.

jalf
I do say I support them for exceptional case in my question. I believe return codes have a place when failure is commonplace.
Steve
+2  A: 

Use exceptions for exceptional error conditions. You have some good arguments for, and I'd like to attack some arguments against.

First, the standard C++ library uses exceptions itself, all over the place. You can't use container classes or iostreams without having them present. Since a lot of the useful features are going to use them, trying to get along without them is going to present a lot of problems.

Second, it isn't hard to write exception-safe code once you've learned how to do it. It requires consistent RAII, but that's how you should write anyway. You should adopt a construct-commit approach, but that is frequently an advantage, and avoids some subtle bugs. (For example, the self-assignment problem disappears entirely with a copy-swap approach.) In my experience, exception-safe code looks better in general. It is something C++ programmers have to learn, but there's lots of things C++ programmers have to learn, and this isn't that much more.

Third, provided you limit exceptions to exceptional cases, there should be minimal effects on performance. And, as Pavel Minaev has pointed out, if you have to pass error codes back with results, there's the possibility of effects on performance, since C++ isn't set up for easy returns of multiple values.

Fourth, it is indeed difficult to make older code exception-safe. However, this is a new project.

So, I see no good reasons not to throw exceptions in exceptional circumstances, and plenty of reasons to do so.

David Thornley
+1  A: 

As a general rule. when recovery is possible and expected then use return codes.

When recovery is not possible or not desired then use exceptions.

Error handling is difficult, writing clean code with and without exceptions is - difficult.

As this is a new project, you don't have to worry about making old code exception safe, however you do have to worry about writing clean clear code.

Do so by using exceptions where appropriate.

Liz Albin
+2  A: 

Since many others have already provided the technical reasons for using exceptions over error codes, I will give a practical one.

I work on a complex system which uses return codes instead of exceptions. Now, this is very well designed code, but I would bet that, on average, about 70% of the code in every function is error handling code. A typically function looks something like this:

long Foo( )
{
    long retCode = MoveStage( );
    if ( retCode != RetCode_OK )
    {
        logger->LogError( __LINE__, __FILE__, "some message" );
        return( retCode );
    }

    int someConfigVar = 0;
    long retCode = GetConfigVar( "SomeVarName", someConfigVar );
    if ( retCode != RetCode_OK )
    {
        logger->LogError( __LINE__, __FILE__, "some message" );
        return( retCode );
    }

    long retCode = DoSomething( );
    if ( retCode != RetCode_OK )
    {
        logger->LogError( __LINE__, __FILE__, "some message" );
        return( retCode );
    }

    // and on and on and on...
}

The code is full of this and is hard to follow. On top of that, in many places the return code is ignored completely because we know that the call will not fail. Every function returns a ret code, so what you would normally return as the output of the function has to be returned as an out parameter. Also, all of these functions just return the retCode on error, so we just bubble that damn retCode to the top if something bad happens. You cannot centralize your error handling strategy this way, it becomes a messy headache.

Ed Swangren
A macro would help clarity in the above code - you could write one that allowed you to say something like LOG_IF_FAIL( DoSomething(), logger, "some message" ) which would remove all the ifs and the lne/file macros.
anon
Sure, but I kept everything simple for the sake of explanation. One macro would not be able to handle all of the error conditions in the real code.
Ed Swangren
A: 
  1. Constructors can't give a return code (except if you are possibly throwing exceptions in a constructor you are in for a world of hurt)

  2. Decouples the failure path (which should be very rare) from the logical code which is cleaner (Opens up a much wider failure path. C++ exceptions are not like C# at all. I love C# exceptions. C++ exceptions are worse than useless. Partly due to implementation, partly due to what c++ is and isn't)

  3. Faster in the non-exceptional case (no checking if/else hundreds of thousands of times) (Not really. You have to check the same number of errors no matter what, you just don't see where they get checked. Also, there are errors that matter and ones that don't or at least matter to your code, so you don't have to check every single thing (do you check to see if new threw an error?))
  4. If someone screws up the return code settings (forgets to return FAIL) it can take a very long time to track down. (I'll take a million such errors over any one of the common errors that come about with C++ exceptions)
  5. Better information from the message contained in the error. (It was pointed out to me that a return enum could do the same for error codes) (Well, how do you implement your error handling? That's up to you, isn't it? You can have exceptions that give no useful information or error codes that log out extemsive data on failure. In C++ they seem to never give useful info in the cases you really want them to)
  6. From Jared Par Impossible to ignore without code to specifically designed to handle it (Sure, but this code is usually useless code. It's like passing a law saying everyone has to have bug free programs. The people who don't don't obey such laws anyway and for the people who do they already handle errors where they should anyway)

As for reasons against:

  1. Wanting your program to actually work at least some of the time.
  2. performance.
  3. Ideology. If it's an error we are not supposed to 'recover' from it. We either die with an error message, log and continue, or completely ignore it. If we could fix it it wouldn't be an error, but a feature...and if you use code like that intentionally no debugging tools in the world are going to save you.

Mostly one.

Charles Eli Cheese