Why should exceptions be used conservatively?
Asked Answered
E

28

80

I often see/hear people say that exceptions should only be used rarely, but never explain why. While that may be true, rationale is normally a glib: "it's called an exception for a reason" which, to me, seems to be the sort of explanation that should never be accepted by a respectable programmer/engineer.

There is a range of problems that an exception can be used to solve. Why is it unwise to use them for control flow? What is the philosophy behind being exceptionally conservative with how they are used? Semantics? Performance? Complexity? Aesthetics? Convention?

I've seen some analysis on performance before, but at a level that would be relevant to some systems and irrelevant to others.

Again, I don't necessarily disagree that they should be saved for special circumstances, but I'm wondering what the consensus rationale is (if such a thing exists).

Emeric answered 16/11, 2009 at 18:46 Comment(8)
Duplicate: #1736646Fashoda
Not a duplicate. The linked example is about whether exception handling is useful at all. This is about the distinction between when to use exceptions and when to use another error reporting mechanism.Epidiascope
That question is not exactly a duplicate of this question. Related, I suppose but not a duplicate.Dor
and how about this one #1385672 ?Heraclitean
There is no consensus rationale. Different people have different opinions about the "appropriateness" of throwing exceptions, and these opinions are generally influenced by the language they develop in. You tagged this question with C++, but I suspect if you tagged it with Java you would get different opinions.Colourable
still duplicate : #729879Petropavlovsk
c++ specific. as the answer can be/is different for different languages.Emeric
No, it shouldn't be a wiki. The answers here require expertise and should be rewarded with rep.Hibben
A
93

The primary point of friction is semantics. Many developers abuse exceptions and throw them at every opportunity. The idea is to use exception for somewhat exceptional situation. For example, wrong user input does not count as an exception because you expect this to happen and ready for that. But if you tried to create a file and there was not enough space on disk, then yes, this is a definite exception.

One other issue is that exceptions are often thrown and swallowed. Developers use this technique to simply "silence" the program and let it run as long as possible until completely collapsing. This is very wrong. If you don't process exceptions, if you don't react appropriately by freeing some resources, if you don't log the exception occurrence or at least not notify the user, then you're not using exception for what they are meant.

Answering directly your question. Exceptions should rarely be used because exceptional situations are rare and exceptions are expensive.

Rare, because you don't expect your program crash at every button press or at every malformed user input. Say, database may suddenly not be accessible, there may not be enough space on disk, some third party service you depend on is offline, this all can happen, but quite rarely, these would be clear exceptional cases.

Expensive, because throwing an exception will interrupt the normal program flow. The runtime will unwind the stack until it finds an appropriate exception handler that can handle the exception. It will also gather the call information all along the way to be passed to the exception object the handler will receive. It all has costs.

This is not to say that there can be no exception to using exceptions (smile). Sometimes it can simplify the code structure if you throw an exception instead of forwarding return codes via many layers. As a simple rule, if you expect some method to be called often and discover some "exceptional" situation half the time then it is better to find another solution. If however you expect normal flow of operation most of the time while this "exceptional" situation can only emerge in some rare circumstances, then it is just fine to throw an exception.

@Comments: Exception can definitely be used in some less-exceptional situations if that could make your code simpler and easier. This option is open but I'd say it comes quite rare in practice.

Why is it unwise to use them for control flow?

Because exceptions disrupt normal "control flow". You raise an exception and normal execution of the program is abandoned potentially leaving objects in inconsistent state and some open resources unfreed. Sure, C# has the using statement which will make sure the object will be disposed even if an exception is thrown from the using body. But let us abstract for the moment from the language. Suppose the framework won't dispose objects for you. You do it manually. You have some system for how to request and free resources and memory. You have agreement system-wide who is responsible for freeing objects and resources in what situations. You have rules how to deal with external libraries. It works great if the program follows the normal operation flow. But suddenly in the middle of execution you throw an exception. Half of the resources are left unfreed. Half have not been requested yet. If the operation was meant to be transactional now it is broken. Your rules for handling resources will not work because those code parts responsible for freeing resources simply won't execute. If anybody else wanted to use those resources they may find them in inconsistent state and crash as well because they could not predict this particular situation.

Say, you wanted a method M() call method N() to do some work and arrange for some resource then return it back to M() which will use it and then dispose it. Fine. Now something goes wrong in N() and it throws an exception you didn't expect in M() so the exception bubbles to the top until it maybe gets caught in some method C() which will have no idea what was happening deep down in N() and whether and how to free some resources.

With throwing exceptions you create a way to bring your program into many new unpredictable intermediate states which are hard to prognose, understand and deal with. It's somewhat similar to using GOTO. It is very hard to design a program that can randomly jump its execution from one location to the other. It will also be hard to maintain and debug it. When the program grows in complexity, you just going to lose an overview of what when and where is happening less to fix it.

Arica answered 16/11, 2009 at 18:49 Comment(15)
This is about as concise an explanation that you could want with a fitting example. +1 to you sir.Promethean
Calling certain use "abuse" is begging the question. (Again, not that I disagree). I'm looking for something more substantial.Emeric
@Adrian McCarthy: Now it does. Thanks for feedback.Arica
If you would compare a function that throws an exception vs a function that returns an error code, the stack unwinding would be the same unless I'm missing something.Emeric
@Catskul: not necessarily. A function return returns control directly to the immediate caller. A thrown exception returns control to the first catch handler for that exception type (or a base class thereof or "...") in the entirety of the current call stack.Taro
It should also be noted that debuggers often break by default on exceptions. The debugger will break a lot if you're using exceptions for conditions that are not unusual.Rigmarole
Debuggers normally break on uncaught exceptions (well, VS does, at least), and don't break on caught ones. And you definitely want to break on uncaught exception before it escapes your program.Doorkeeper
@jon: That would only happen if it were uncaught and needed to escape more scopes to reach error handling. In that same case using return values, (and also needing to be passed down through multiple scopes) the same amount of stack unwinding would occur.Emeric
-1 sorry. It's pointless to talk about what exception facilities are "meant" for. They are just a mechanism, one that can be used for handling exceptional situations like running out of memory etc. -- but that doesn't mean they should not be used for other purposes too. What I want to see is an explanation of why they should not be used for other purposes. Although your answer does talk about that a little, it's mixed in with a lot of talk about what exceptions are "meant" for.Maxiemaxilla
@j_random_hacker: Added an explanation.Arica
@Catskul. Well, yes the same amount of stack unwinding would occur, but in the exception case the call stack has to be traversed to determine the appropriate handler. This is an extra cost, which was the original point. Also, see codeproject.com/KB/cpp/exceptionhandler.aspxTaro
Actually the Boost team state exceptions should have no overhead and can actually be faster than code which tests for errors and returns back down the stack - section 2 of boost.org/community/exception_safety.html.Taro
@Developer Art: Thanks for the update. One reason you gave that I haven't seen mentioned yet is that in practice, we live in an imperfect world where not all of the surrounding code is necessarily exception-safe, and without having that guarantee throwing exceptions willy-nilly is very dangerous. +1.Maxiemaxilla
@CatSkul having a catch at each level is an emulation of the error code idiom. Exceptions are not here to duplicate this idiom. The unwind is meant to jump to the location where exceptions will be handled, and error codes are intended to itterate down the stack while allowing parent functions to react -- this reaction is for error conditions which are common. There is a large difference here -- the exception idiom and the error-code idiom are meant to model different problems.Adamson
Maybe you could comment when it is better to thrown an exception rather than terminate the program immediatelly like this: printf("Some useful log message\n"); exit(EXIT_FAILURE) and when the latter is better?Orfurd
L
63

While "throw exceptions in exceptional cirumstances" is the glib answer, you can actually define what those circumstances are: when preconditions are satisfied, but postconditions cannot be satisfied. This allows you to write stricter, tighter, and more useful postconditions without sacrificing error-handling; otherwise, without exceptions, you have to change the postcondition to allow for every possible error state.

  • Preconditions must be true before calling a function.
  • Postcondition is what the function guarantees after it returns.
  • Exception safety states how exceptions affect the internal consistency of a function or data structure, and often deal with behavior passed in from outside (e.g. functor, ctor of a template parameter, etc.).

Constructors

There's very little you can say about every constructor for every class that could possibly be written in C++, but there are a few things. Chief among them is that constructed objects (i.e. for which the constructor succeeded by returning) will be destructed. You cannot modify this postcondition because the language assumes it is true, and will call destructors automatically. (Technically you can accept the possibility of undefined behavior for which the language makes no guarantees about anything, but that is probably better covered elsewhere.)

The only alternative to throwing an exception when a constructor cannot succeed is to modify the basic definition of the class (the "class invariant") to allow valid "null" or zombie states and thus allow the constructor to "succeed" by constructing a zombie.

Zombie example

An example of this zombie modification is std::ifstream, and you must always check its state before you can use it. Because std::string, for example, doesn't, you are always guaranteed that you can use it immediately after construction. Imagine if you had to write code such as this example, and if you forgot to check for the zombie state, you'd either silently get incorrect results or corrupt other parts of your program:

string s = "abc";
if (s.memory_allocation_succeeded()) {
  do_something_with(s); // etc.
}

Even naming that method is a good example of how you must modify the class' invariant and interface for a situation string can neither predict nor handle itself.

Validating input example

Let's address a common example: validating user input. Just because we want to allow for failed input doesn't mean the parsing function needs to include that in its postcondition. It does mean our handler needs to check if the parser fails, however.

// boost::lexical_cast<int>() is the parsing function here
void show_square() {
  using namespace std;
  assert(cin); // precondition for show_square()
  cout << "Enter a number: ";
  string line;
  if (!getline(cin, line)) { // EOF on cin
    // error handling omitted, that EOF will not be reached is considered
    // part of the precondition for this function for the sake of example
    //
    // note: the below Python version throws an EOFError from raw_input
    //  in this case, and handling this situation is the only difference
    //  between the two
  }
  int n;
  try {
    n = boost::lexical_cast<int>(line);
    // lexical_cast returns an int
    // if line == "abc", it obviously cannot meet that postcondition
  }
  catch (boost::bad_lexical_cast&) {
    cout << "I can't do that, Dave.\n";
    return;
  }
  cout << n * n << '\n';
}

Unfortunately, this shows two examples of how C++'s scoping requires you to break RAII/SBRM. An example in Python which doesn't have that problem and shows something I wish C++ had – try-else:

# int() is the parsing "function" here
def show_square():
  line = raw_input("Enter a number: ") # same precondition as above
  # however, here raw_input will throw an exception instead of us
  # using assert
  try:
    n = int(line)
  except ValueError:
    print "I can't do that, Dave."
  else:
    print n * n

Preconditions

Preconditions don't strictly have to be checked – violating one always indicates a logic failure, and they are the caller's responsibility – but if you do check them, then throwing an exception is appropriate. (In some cases it's more appropriate to return garbage or crash the program; though those actions can be horribly wrong in other contexts. How to best handle undefined behavior is another topic.)

In particular, contrast the std::logic_error and std::runtime_error branches of the stdlib exception hierarchy. The former is often used for precondition violations, while the latter is more suited for postcondition violations.

Lancewood answered 16/11, 2009 at 19:6 Comment(26)
Decent answer, but you are basically just stating a rule, and not providing a rationale. Is your rational then: Convention and style?Emeric
+1. This is how exceptions were designed to be used, and using them in this way actually improves the readability and reusability of code (IMHO).Whang
Catskul: the first is the definition of exceptional circumstances, not rationale nor convention. If you cannot guarantee postconditions you cannot return. Without exceptions you have to make the postcondition extremely broad to include all error states, to the point where it's practically useless. It looks like I didn't answer your literal question of "why should they be rare" because I don't look at them that way. They are a tool that allows me to tighten postconditions usefully while still allowing for errors to occur (..in exceptional circumstances :).Lancewood
the last paragraph and 'must' is a myth. It is convenient but never a must.Louie
MajkaRa: It is accurate. A constructor does not have a return value, and cannot provide one. Either it succeeds in constructing the object, or it fails by throwing an exception.Lancewood
@R.Pate, but it misses the implicit question of why only use them that way.Emeric
That answer is implied: indicating the inability to fulfill a postcondition when that is not true is a lie.Lancewood
@Catskul: Yes! If I could upvote your comment 100 times I would. R. Pate: Exception throwing and handling is just a mechanism provided by a language. That mechanism can be used for escaping when postcondition violation is unavoidable -- but who or what says that they may be used only for that?Maxiemaxilla
@R. Pate: Compare: an oven is designed for cooking food. But it can also be used to heat a room. The question being asked is: Why not use an oven to heat a room? (In this case, the answer is: in fact you can! But a purpose-built heater will generally provide faster warming.)Maxiemaxilla
Proof by analogy is problematic. Yours doesn't fit this situation.Lancewood
You must remember that any function which throws an exception is not---by definition---fulfilling its postcondition, as the postcondition includes that it returns and the returned value. I am not trying to answer "Why only use them rarely?", because that's not the heart of the question asked. "What is the philosophy behind .. how they are used?", from the question text, is the core of the issue that I address.Lancewood
@j_random_hacker: indeed, my gf's parents have an Aga, which is an oven that heats the room. I think that in the analogy, the Aga is Python, which throws exceptions for any old thing ;-)Celina
Python is a good example of the clarity you can get with better postconditions enabled by exceptions, actually. Compare using int("42") with C's strtol. Yes, you can write spaghetti code, but you can do that without exceptions too. You can choose to design carefully, so that you end up with better code, as well.Lancewood
@R.Pate: the definition you chose to link to doesn't say the function returns, it says postconditions are guaranteed after the function executes. For instance, the strong exception guarantee states, "either the function returns, and blah_whatever, or else an exception is thrown and all relevant objects are in the same state as before the call was made". I think it's quite helpful to describe that as a postcondition - otherwise each function needs preconditions, postconditions, and postexceptionconditions.Celina
... but of course if that's not what whoever invented the term "postcondition" wants it to mean in languages with exceptions, then so be it. All you've done, though, is punted the question "why should I use exceptions for X but not Y" over to "why should Y imply some postcondition, but X does not". To which the answer is still, "um. Just because, alright?". Or at least, it was before you made that comment about Python, saying that int() has "better postconditions" than strtol. So now we want to drill into what's a good postcondition, and what's a good exception situation :-)Celina
Steve: see my above comment about what it is I'm addressing. I'm not linking to Wikipedia as the final authority, but only as a reference to easily get more information on the subject.Lancewood
Exception safety does not overlap the way you're suggesting, and the principle way it has become popular is with templates, where you don't even know what exceptions may be thrown, as you're depending on the actions of some object or type given to you.Lancewood
If you must order them: precondtions are checked before the call, exception safety matters "during" the call, and postconditions specify what you know about the return value. Inability to meet the postcondition is, of course, exceptional.Lancewood
+1 anyway, and I hope that, reading your answer, more people start thinking in pre/postcondition way.Hamo
again, it is a myth and 'must' is too strong. passing a reference argument to a constructor 'downgrades' you to pre/post verification and error code land. while there, note that plenty of std libs allow you to replace the new handler. ok, while it does break a very useful contract, ie. that the constructor has run fully or not, the workaround is usable and pretty rare. again, the 'must' just doesn't make any sense, all you end up with is machine code and that 'must' is a myth which was never correct.Louie
MajkaRa: it is built into the langauge that when a constructor returns the object has been constructed. You can change the definition of your class, but you cannot change that constructors guarantee to construct objects. This is exactly my same point as with postconditions! Instead of allowing error states in your "valid" return value, you can use exceptions. C's atoi is a great example of the failure to account for that.Lancewood
It is perfectly possible to guarantee that an instance of type has/not completely constructed without exceptions at all. And while it can look like a cludge, it outperforms, is quite rare plus the error codes are a universal/cross-env mechanism (which is all there is out there, on any box). All of this while exceptions are pretty much limited to your language, environment and often even compiler choice. That's why 'must' and 'only' are missing the point.Louie
@R. Pate: In case your response "Proof by analogy..." was directed at me: What I was trying to say is that I think your use of exceptions to escape from postconditions that can't be satisfied is a perfectly good and valid use of the exception-handling mechanism provided by the language (or equivalently, you described a good way to cook a meal using an oven). What I'm asking is whether there are other useful things that might be done with the exception-handling mechanism provided by the language (other things that could be done with the oven besides cooking).Maxiemaxilla
+1. Pre/Post condition is the BEST explanation I have ever heard.Curriculum
A lot of people are saying "but that just boils down to convention/semantics." Yeah, that's right. But convention and semantics matter because they have deep implications for complexity, usability, and maintainability. After all, isn't the decision of whether to formally define pre-conditions and post-conditions also just a matter of convention and semantics? And yet their use can make your code significantly easier to use and maintain.Vying
Beautiful. Just want note that it's your response to Catskul on Nov 16 '09 at 23:34 that really made this click into place for me. Maybe that could be incorporated into the answer somehow? Or maybe it's fine to leave it as is, but that additional comment was really crucial.Rattly
S
40
  1. Expensive
    kernel calls (or other system API invocations) to manage kernel (system) signal interfaces
  2. Hard to analyze
    Many of the problems of the goto statement apply to exceptions. They jump over potentially large amounts of code often in multiple routines and source files. This is not always apparent from reading the intermediate source code. (It is in Java.)
  3. Not always anticipated by intermediate code
    The code that gets jumped over may or may not have been written with the possibility of an exception exit in mind. If originally so written, it may not have been maintained with that in mind. Think: memory leaks, file descriptor leaks, socket leaks, who knows?
  4. Maintenance complications
    It's harder to maintain code that jumps around processing exceptions.
Stormie answered 17/11, 2009 at 3:18 Comment(7)
+1, good reasons in general. But note that the cost of stack unwinding and calling of destructors (at least) is paid by any functionally equivalent error-handling mechanism, e.g. by testing for and returning error codes as in C.Maxiemaxilla
I'm going to mark this as the answer even though I agree with j_random. Stack unwinding and resource de/allocation would be the same in every functionally equivalent mechanism. If you agree, when you see this, just chop those off the list to make the answer a little better.Emeric
Stack unwinding is worth mentioning here: runtime while unwind until it finds an appropriate exception handler, which is not necessarily the caller, eventually causing more deallocations etc.Backbone
Julien: j_random's comment points out there is no comparative savings: int f() { char* s = malloc(...); if (some_func() == error) { free(s); return error; } ... } You have to pay to unwind the stack whether you do it manually or through exceptions. You can't compare using exceptions to no error handling at all.Lancewood
I agree that my mention of stack unwinding is going a bit far, since that has to happen eventually. I was thinking of something like a "proof by contradiction". Assume exceptions are cheap, therefore we can use them in control flow, therefore we can unwind to an exception handler, and then call back into the logic loop again.......Stormie
1. Expensive: premature optimization is the root of all evil. 2. Hard to analyze: as compared to nested layers of error return code? I respectfully disagree. 3. Not always anticipated by intermediate code: as compared to nested layers not always dealing with and translating errors reasonably? Newbies fail at both. 4. Maintenance complications: how? Are dependencies mediated by error codes any easier to maintain? ...but this seems to be one of those areas where developers of either school have difficulty accepting each others' arguments. As with anything, error handling design is a tradeoff.Tereus
I would agree that this is a complex matter and that it is hard to answer accurately with generalities. But keep in mind that the original question simply asked why the anti-exception advice was given, not for an explanation of overall best practice.Stormie
M
22

Throwing an exception is, to some extent, similar to a goto statement. Do that for flow control, and you end with incomprehensible spaghetti code. Even worse, in some cases you do not even know where exactly the jump goes to (i.e. if you are not catching the exception in the given context). This blatantly violates the "least surprise" principle that enhances maintainability.

Maguire answered 16/11, 2009 at 18:52 Comment(7)
How is it possible to use an exception for any purpose other than flow control? Even in an exceptional situation, they're still a flow control mechanism, with consequences for the clarity of your code (assumed here: incomprehensible). I suppose you could use exceptions but ban catch, although in that case you almost might as well just call std::terminate() yourself. Overall, this argument seems to me to say "never use exceptions", rather than "only use exceptions rarely".Celina
That seems to put it on equal footing with a "return" statement inside a function. But it's worse in that at least a return statement is explicit. You can't look at a function call and know that it will produce a throw.Emeric
return statement inside function takes you out of the function. Exceptions takes you who knows how many layers up - there is no simple way to find out.Oeildeboeuf
Steve: I understand your point, but that's not what I meant. Exceptions are ok for unexpected situations. Some people abuse them as "early returns", maybe even as some kind of switch statement.Maguire
I think this question assumes that "unexpected" isn't explanatory enough. I agree to an extent. If some condition stops a function from completing as desired, then either your program handles that situation correctly, or it doesn't. If it handles it, then it's "expected", and the code needs to be comprehensible. If it doesn't handle it correctly, you're in trouble. Unless you let the exception terminate your program, some level of your code must "expect" it. And yet in practice, as I say in my answer, it's a good rule despite being theoretically unsatisfactory.Celina
Plus of course wrappers can convert a throwing function into an error-returning one, or vice versa. So if your caller disagrees with your idea of "unexpected" it doesn't necessarily break their code comprehensibility. It might sacrifice some performance where they're provoking a lot of conditions which you think are "unexpected", but they think are normal and recoverable, and hence catch and convert to an errno or whatever.Celina
+1, the "least surprise" principle does enhance maintainability.Maxiemaxilla
C
16

Exceptions make it harder to reason about the state of your program. In C++ for instance, you have to do extra thinking to ensure your functions are strongly exception safe, than you would have to do if they didn't need to be.

The reason is that without exceptions, a function call can either return, or it can terminate the program first. With exceptions, a function call can either return, or it can terminate the program, or it can jump to a catch block somewhere. So you can no longer follow the flow of control just by looking at the code in front of you. You need to know if the functions called can throw. You may need to know what can be thrown and where it's caught, depending on whether you care where control goes, or only care that it leaves the current scope.

For this reason, people say "don't use exceptions unless the situation is really exceptional". When you get down to it, "really exceptional" means "some situation has occurred where the benefits of handling it with an error return value are outweighed by the costs". So yes, this is something of an empty statement, although once you have some instincts for "really exceptional", it becomes a good rule of thumb. When people talk about flow control, they mean that the ability to reason locally (without reference to catch blocks) is a benefit of return values.

Java has a wider definition of "really exceptional" than C++. C++ programmers are more likely to want to look at the return value of a function than Java programmers, so in Java "really exceptional" might mean "I can't return a non-null object as the result of this function". In C++, it's more likely to mean "I very much doubt my caller can continue". So a Java stream throws if it can't read a file, whereas a C++ stream (by default) returns a value indicating error. In all cases, though, it is a matter of what code you are willing to force your caller to have to write. So it is indeed a matter of coding style: you have to reach a consensus what your code should look like, and how much "error-checking" code you want to write against how much "exception-safety" reasoning you want to do.

The broad consensus across all languages seems to be that this is best done in terms of how recoverable the error is likely to be (since unrecoverable errors result in no code with exceptions, but still need a check-and-return-your-own-error in code which uses error returns). So people come to expect "this function I call throws an exception" to mean "I can't continue", not just "it can't continue". That's not inherent in exceptions, it's just a custom, but like any good programming practice, it's a custom advocated by smart people who've tried it the other way and not enjoyed the results. I too have had bad experiences throwing too many exceptions. So personally, I do think in terms of "really exceptional", unless something about the situation makes an exception particularly attractive.

Btw, quite aside from reasoning about the state of your code, there are also performance implications. Exceptions are usually cheap now, in languages where you're entitled to care about performance. They can be faster than multiple levels of "oh, the result's an error, I'd best exit myself with an error too, then". In the bad old days, there were real fears that throwing an exception, catching it, and carrying on with the next thing, would make what you're doing so slow as to be useless. So in that case, "really exceptional" means, "the situation is so bad that horrific performance no longer matters". That's no longer the case (although an exception in a tight loop is still noticeable) and hopefully indicates why the definition of "really exceptional" needs to be flexible.

Celina answered 16/11, 2009 at 19:28 Comment(12)
"In C++ for instance, you have to do extra thinking to ensure your functions are strongly exception safe, than you would have to do if they didn't need to be." - given that basic C++ constructs (such as new) and the standard library all throw exceptions, I don't see how not using exceptions in your code relieves you from writing exception-safe code regardless.Doorkeeper
You'd have to ask Google. But yes, point taken, if your function isn't nothrow, then your callers are going to have to do some work, and adding extra conditions which cause exceptions doesn't make it "more exception-throwing". But out-of-memory is a bit of a special case anyway. It's common to have programs which don't handle it, and just shut down instead. You could have a coding standard which says, "don't use exceptions, don't give exception guarantees, if new throws then we'll terminate, and we'll use compilers that don't unwind the stack". Not high-concept, but it would fly.Celina
Once you use a compiler that doesn't unwind the stack (or otherwise disable exceptions in it), it is, technically speaking, no longer C++ :)Doorkeeper
Doesn't unwind the stack on an uncaught exception, I mean.Celina
How does it know it's uncaught unless it unwinds the stack to find an catch block?Myrmecophagous
Not my problem, guv, the standard says it's implementation-defined whether the stack is unwound (in the sense of destructors being called) when an uncaught exception is thrown. But one possible exception implementation mechanism is to create a linked list of exception frames as you descend the stack. It would be perfectly plausible to, as you do this, keep a separate list of frames with a catch block. If this list is empty when an exception is thrown, of if none of the catch blocks matches the exception, the implementation can call terminate immediately without unwinding the stack.Celina
Finally an answer I can feel good about +1ing. Summarising: Exceptions are not inherently "for" anything, they're just a mechanism; they create a big cognitive burden for all future maintainers, so it's best not to use them unnecessarily; experience suggests that the tipping point is for code that "feels exceptional"; what "feels exceptional" isn't cast in stone, rather it's cultural and varies between languages; performance concerns are largely outdated. Excellent.Maxiemaxilla
... it's commonly imagined that the way destructor-on-exception works is to give every variable scope an implicit secret try/catch(...) { /*call destructors*/; throw; } around it. But for efficiency reasons that's not how it's usually done. I don't know which compilers call destructors on terminate and which don't, it's never arisen. I'm just saying that if you were determined, you could avoid the need for exception-safe code in this way, and you could pick a conforming implementation where even destructors wouldn't necessarily have to be "safe to call" following an exception.Celina
@jmucchiello: as evidence, I point to the fact that VS debugger is configurable to break on an uncaught exception. It does this before calling any destructors or climbing back up the stack, because it's helpful to study the context where the throw occurred. Now, I don't know whether when you hit "continue" it then calls the destructors - I expect it's generally more helpful if it does. But I do know that it can tell the exception is uncaught without unwinding.Celina
Win32 SEH (on which VC++ exceptions are built) does two passes - first one to determine which handler will run, and second one to rewind the stack. Debugger can break between those two events.Doorkeeper
I disagree with the statement "With exceptions, a function call can either return, or it can terminate the program, or it can jump to a catch block somewhere". That's absolutely the wrong way to think about it. Rather than thinking exceptions cause you to jump to a catch block somewhere, you should think that exceptions cause your stack to unwind. After that, it's someone else's problem; and if you don't want a stack unwind, then you resort to catching the exception.Dispeople
It's "someone else's problem" if you're narrowly trying to analyse the routine. It's "my problem" if I'm trying to analyse the program - in that case, it matters where control flow goes next. The gap between the two is where errors occur trying to reason about the state of code which uses exceptions. I agree, though, that I've abused the word "jump" a bit, since it's nothing like a longjmp, or an assembler jump instruction.Celina
C
11

There really is no consensus. The whole issue is somewhat subjective, because the "appropriateness" of throwing an exception is often suggested by existing practices within the standard library of the language itself. The C++ standard library throws exceptions a lot less frequently than say, the Java standard library, which almost always prefers exceptions, even for expected errors such as invalid user input (e.g. Scanner.nextInt). This, I believe, significantly influences developer opinions about when it is appropriate to throw an exception.

As a C++ programmer, I personally prefer to reserve exceptions for very "exceptional" circumstances, e.g. out of memory, out of disk-space, the apocalypse happened, etc. But I don't insist that this is the absolute correct way to do things.

Colourable answered 16/11, 2009 at 18:56 Comment(6)
I think there is a consensus of sorts, but that perhaps the consensus is based more on convention rather than sound reasoning. There may be sound reasons only to use exceptions for exceptional circumstances, but most developers are not really aware of the reasons.Rigmarole
Agreed - you hear about "exceptions are for exceptional circumstances" often, but no-one bothers to properly define what an "exceptional situation" is - it's largely just custom, and definitely language specific. Heck, in Python, iterators use exception to signal end of sequence, and it's considered perfectly normal!Doorkeeper
@Qwertie: that is exactly I asked this question. I am/was trying to determine how much of it is convention and how much of it is technical.Emeric
+1. There are no hard and fast rules, just conventions -- but it's useful to adhere to the conventions of others who use your language, since it makes it easier for programmers to understand each others' code.Maxiemaxilla
"the apocalypse happened" - never mind exceptions, if anything justifies U.B, that does ;-)Celina
Catskul: Almost all of programming is convention. Technically, we don't even need exceptions, or really much at all. If it doesn't involve NP-completeness, big-O/theta/little-o, or Universal Turing Machines, it's probably convention. :-)Ingalls
C
7

EDIT 11/20/2009:

I was just reading this MSDN article on improving managed code performance and this part reminded me of this question:

The performance cost of throwing an exception is significant. Although structured exception handling is the recommended way of handling error conditions, make sure you use exceptions only in exceptional circumstances when error conditions occur. Do not use exceptions for regular control flow.

Of course, this is only for .NET, and it's also directed specifically at those developing high-performance applications (like myself); so it's obviously not a universal truth. Still, there are a lot of us .NET developers out there, so I felt it was worth noting.

EDIT:

OK, first of all, let's get one thing straight: I have no intention of picking a fight with anyone over the performance question. In general, in fact, I am inclined to agree with those who believe premature optimization is a sin. However, let me just make two points:

  1. The poster is asking for an objective rationale behind the conventional wisdom that exceptions should be used sparingly. We can discuss readability and proper design all we want; but these are subjective matters with people ready to argue on either side. I think the poster is aware of this. The fact is that using exceptions to control program flow is often an inefficient way of doing things. No, not always, but often. This is why it's reasonable advice to use exceptions sparingly, just like it's good advice to eat red meat or drink wine sparingly.

  2. There is a difference between optimizing for no good reason and writing efficient code. The corollary to this is that there's a difference between writing something that is robust, if not optimized, and something that is just plain inefficient. Sometimes I think when people argue over things like exception handling they're really just talking past each other, because they are discussing fundamentally different things.

To illustrate my point, consider the following C# code examples.

Example 1: Detecting invalid user input

This is an example of what I'd call exception abuse.

int value = -1;
string input = GetInput();
bool inputChecksOut = false;

while (!inputChecksOut) {
    try {
        value = int.Parse(input);
        inputChecksOut = true;

    } catch (FormatException) {
        input = GetInput();
    }
}

This code is, to me, ridiculous. Of course it works. No one's arguing with that. But it should be something like:

int value = -1;
string input = GetInput();

while (!int.TryParse(input, out value)) {
    input = GetInput();
}

Example 2: Checking for the existence of a file

I think this scenario is actually very common. It certainly seems a lot more "acceptable" to a lot of people, since it deals with file I/O:

string text = null;
string path = GetInput();
bool inputChecksOut = false;

while (!inputChecksOut) {
    try {
        using (FileStream fs = new FileStream(path, FileMode.Open)) {
            using (StreamReader sr = new StreamReader(fs)) {
                text = sr.ReadToEnd();
            }
        }

        inputChecksOut = true;

    } catch (FileNotFoundException) {
        path = GetInput();
    }
}

This seems reasonable enough, right? We're trying to open a file; if it's not there, we catch that exception and try to open a different file... What's wrong with that?

Nothing, really. But consider this alternative, which doesn't throw any exceptions:

string text = null;
string path = GetInput();

while (!File.Exists(path)) path = GetInput();

using (FileStream fs = new FileStream(path, FileMode.Open)) {
    using (StreamReader sr = new StreamReader(fs)) {
        text = sr.ReadToEnd();
    }
}

Of course, if the performance of these two approaches were actually the same, this really would be purely a doctrinal issue. So, let's take a look. For the first code example, I made a list of 10000 random strings, none of which represented a proper integer, and then added a valid integer string onto the very end. Using both of the above approaches, these were my results:

Using try/catch block: 25.455 seconds
Using int.TryParse: 1.637 milliseconds

For the second example, I did basically the same thing: made a list of 10000 random strings, none of which was a valid path, then added a valid path onto the very end. These were the results:

Using try/catch block: 29.989 seconds
Using File.Exists: 22.820 milliseconds

A lot of people would respond to this by saying, "Yeah, well, throwing and catching 10,000 exceptions is extremely unrealistic; this exaggerates the results." Of course it does. The difference between throwing one exception and handling bad input on your own is not going to be noticeable to the user. The fact remains that using exceptions is, in these two case, from 1,000 to over 10,000 times slower than the alternative approaches that are just as readable -- if not more so.

That's why I included the example of the GetNine() method below. It isn't that it's intolerably slow or unacceptably slow; it's that it's slower than it should be... for no good reason.

Again, these are just two examples. Of course there will be times when the performance hit of using exceptions is not this severe (Pavel's right; after all, it does depend on the implementation). All I'm saying is: let's face the facts, guys -- in cases like the one above, throwing and catching an exception is analogous to GetNine(); it's just an inefficient way of doing something that could easily be done better.


You are asking for a rationale as if this is one of those situations where everyone's jumped on a bandwagon without knowing why. But in fact the answer is obvious, and I think you know it already. Exception handling has horrendous performance.

OK, maybe it's fine for your particularly business scenario, but relatively speaking, throwing/catching an exception introduces way more overhead than is necessary in many, many cases. You know it, I know it: most of the time, if you're using exceptions to control program flow, you're just writing slow code.

You might as well ask: why is this code bad?

private int GetNine() {
    for (int i = 0; i < 10; i++) {
        if (i == 9) return i;
    }
}

I would bet that if you profiled this function you'd find it performs quite acceptably fast for your typical business application. That doesn't change the fact that it's a horribly inefficient way of accomplishing something that could be done a lot better.

That's what people mean when they talk about exception "abuse."

Cora answered 16/11, 2009 at 19:57 Comment(12)
"Exception handling has horrendous performance." - it's an implementation detail, and doesn't hold true for all languages, and even for C++ specifically, for all implementations.Doorkeeper
"it's an implementation detail" I can not remember how often I heard this argument - and it just stinks. Please remember: All Computer stuff is about a sum of implementation details. And also: It is not wise to use a screw driver as replacement of a hammer -- that is what people do, that talk to much about "just implementation details".Blim
And yet Python happily uses exceptions for generic flow control, because the perf hit for their implementation isn't anywhere as bad. So if your hammer looks like a screwdriver, well, blame the hammer...Doorkeeper
I am aware of this behaviour of Python. Python indeed is rather special, since Exceptions are integrated in the runtime in a way that makes their overhead rather low in comparison to other language features -- and Pythons main design goal never was speed. Still I don't believe that you should misuse Exceptions that way -- specially of course not in languages that are not build like Python, since you are always better of, using the tools how they are meant to be used.Blim
@Pavel: "It's an implementation detail"... Fair enough. But you say this as if we should therefore disregard it. Practically everything is an implementation detail. The fact is that when developers often use exceptions to control program flow, it is very often simply a less efficient way of handling, for example, invalid input. Anyway, my point is not that this causes an unacceptable performance penalty, but simply that it's a suboptimal approach.Cora
Tempted to -1, since I think the performance loss is grossly overestimated > 99% of the time. Although your GetNine() function makes any good programmer feel itchy, I'll bet that 99% of the time it does a good enough of getting 9. As a real-world example, I'm told that Linux kernel sources contain a ton of code where things are done in the "dumbest possible way" -- linear scans through fixed-size arrays or linked lists, when tree structures or hashtables would be faster. "Dumb" is just fine when n is small.Maxiemaxilla
@j_random_hacker: You're right, GetNine does its job just fine. My point is that there's no reason to use it. It isn't like it's the "quick and easy" way of getting something done, while the more "correct" approach would take a lot more effort. In my experience, it's often the case that the "correct" approach would actually take less effort, or about the same. Anyway, to me performance is the only objective reason why using exceptions left and right is discouraged. As for whether the performance loss is "grossly overestimated," that is in fact subjective.Cora
@Dan: Thanks for the clarification, I agree with a lot of what you say. Although performance may be the only objective criterion, I think it's often much less useful than other (unfortunately subjective) criteria like maintainability. (As an aside, I was surprised to see just how slow the exceptions made things in your tests -- I hacked together the moral equivalent of your Example 1 in C++, and the exception-using version is ~10 times slower using g++ or only about twice as slow using MSVC++ on my machine.)Maxiemaxilla
@j_random_hacker: Your aside is very interesting, and really drives home Pavel's point about the performance hit depending on the implementation. One thing I would very much doubt is that someone could find a scenario in which using exceptions actually outperforms the alternative (where an alternative exists, at least, such as in my examples).Cora
Hehe imagine that... Exceptions as an optimisation technique :)Maxiemaxilla
I'm surprised this line of reasoning isn't more common. Writing slow code is fine, when it's fast enough and there's a benefit besides performance to writing it that way… but why do it with no reason at all?Virago
Nitpick: Example 2, the non-exception version is a race condition.Topnotch
S
7

I don't think, that exceptions should rarely be used. But.

Not all teams and projects are ready to use exceptions. Usage of exceptions requires high qualification of programmers, special technics and lack of big legacy non exception-safe code. If you have huge old codebase, then it almost always is not exception-safe. I'm sure that you do not want to rewrite it.

If you are going to use exceptions extensively, then:

  • be prepared to teach your people about what exception safety is
  • you should not use raw memory management
  • use RAII extensively

From the other hand, using exceptions in new projects with strong team may make code cleaner, easier to maintain, and even faster:

  • you will not miss or ignore errors
  • you haven't to write that checks of return codes, without actually knowing what to do with wrong code at low-level
  • when you are forced to write exception-safe code, it becomes more structured
Stratify answered 16/11, 2009 at 20:36 Comment(2)
I especially liked your mention of the fact that "not all teams ... are ready to use exceptions". Exceptions are definitely something that seem easy to do, but are extremely hard to do right, and that's part of what makes them dangerous.Maxiemaxilla
+1 for "when you are forced to write exception-safe code, it becomes more structured". Exception-based code is forced to have more structure, and I find it actually much easier to reason about objects and invariants when it's impossible to ignore them. In fact, I believe strong exception-safety is all about writing nearly-reversible code, which makes it really easy to avoid indeterminate state.Dispeople
A
6

It's not that exceptions should rarely be used. It's just that they should only be thrown in exceptional circumstances. For example, if a user enters the wrong password, that's not exceptional.

The reason is simple: exceptions exit a function abruptly, and propagate up the stack to a catch block. This process is very computationally expensive: C++ builds its exception system to have little overhead on "normal" function calls, so when an exception is raised, it has to do a lot of work to find where to go. Moreover, since every line of code could possibly raise an exception. If we have some function f that raises exceptions often, we now have to take care to use our try/catch blocks around every call of f. That's a pretty bad interface/implementation coupling.

Acromion answered 16/11, 2009 at 18:52 Comment(2)
You have essentially repeated the "they're called exceptions for a reason" rationale. I'm looking for people to flesh this out into something more substantial.Emeric
What does "exceptional circumstances" mean? In practice, my program has to handle actual IOExceptions more frequently than bad user passwords. Does that mean you think I should make BadUserPassword an exception, or that the stdlib guys shouldn't have made IOException an Exception? "Very computationally expensive" can't be the actual reason, because I've never seen a program (and certainly not mine) for which handling an incorrect password, via any control mechanism, was a performance bottleneck.Ingalls
H
6

All of the rules of thumb about exceptions come down to subjective terms. You shouldn't expect to get hard and fast definitions of when to use them and when not to. "Only in exceptional circumstances". Nice circular definition: exceptions are for exceptional circumstances.

When to use exceptions falls into the same bucket as "how do I know whether this code is one class or two?" It's partly a stylistic issue, partly a preference. Exceptions are a tool. They can be used and abused, and finding the line between the two is part of the art and skill of programming.

There are lots of opinions, and tradeoffs to be made. Find something that speaks to you, and follow it.

Hudnall answered 16/11, 2009 at 19:15 Comment(0)
U
5

I mentioned this issue in an article on C++ exceptions.

The relevant part:

Almost always, using exceptions to affect the "normal" flow is a bad idea. As we already discussed in section 3.1, exceptions generate invisible code paths. These code paths are arguably acceptable if they get executed only in the error handling scenarios. However, if we use exceptions for any other purpose, our "normal" code execution is divided into a visible and invisible part and it makes code very hard to read, understand and extend.

Uyekawa answered 16/11, 2009 at 20:21 Comment(6)
You see, I came to C++ from C, and I think that "error handling scenarios" are normal. They may not be executed frequently, but I spend more time thinking about them than I do about non-error code paths. So again, I do generally agree with the sentiment, but it doesn't get us any closer to defining "normal", and how we can deduce from the definition of "normal" that using exceptions is a bad option.Celina
I also came to C++ from C, but even in C I made a distinction between "normal" flow end error handling. If you call i.e. fopen(), there is a branch that deals with the success case and that is "normal", and one that deals with possible causes of failure, and that's error handling.Uyekawa
Right, but I don't mysteriously get any clever when reasoning about error code. So if "invisible code paths" are very hard to read, understand and extend for "normal" code, then I don't see at all why applying the word "error" makes them "arguably acceptable". Error situations happen all the time, in my experience, and this makes them "normal". If you can figure out exceptions in error situations, then you can figure them out in non-error situations. If you can't, you can't, and all you've done is made your error code impenetrable but ignored that fact because it's "only errors".Celina
Example: you have a function where something needs to get done, but you're going to try a whole load of different approaches, and you don't know which will succeed, and each of them might try some extra sub-approaches, and so on, until something works. Then I would argue that failure is "normal", and success is "exceptional" although clearly not an error. Throwing an exception on success is no harder to understand than throwing an exception on horrible error is in most programs - you ensure that only one bit of code needs to know what to do next, and you jump straight there.Celina
@onebyone: Your 2nd comment actually makes a good point I haven't seen made elsewhere. I think the answer to that is that (as you effectively said in your own answer) using exceptions for error conditions has been absorbed into the "standard practices" of many languages, making it a useful guideline to adhere to even if the original reasons for doing that were misguided.Maxiemaxilla
If your errors happen all the time and the success is exceptional, exceptions are obviously a wrong mechanism for the job. I throw an exception, for instance, if I can't open a file that really should be there, but maybe a user deleted it. Or, if I am reading an XML file that was also written by me but find it invalid.Uyekawa
U
5

My approach to error handling is that there are three fundamental types of errors:

  • An odd situation that can be handled at the error site. This might be if a user inputs an invalid input at a command line prompt. The correct behavior is simply to complain to the user and loop in this case. Another situation might be a divide-by-zero. These situations aren't really error situations, and are usually caused by faulty input.
  • A situation like the previous kind, but one that can't be handled at the error site. For instance, if you have a function that takes a filename and parses the file with that name, it might not be able to open the file. In this case, it can't deal with the error. This is when exceptions shine. Rather than use the C approach (return an invalid value as a flag and set a global error variable to indicate the problem), the code can instead throw an exception. The calling code will then be able to deal with the exception - for instance to prompt the user for another filename.
  • A situation that Should Not Happen. This is when a class invariant is violated, or a function receives an invalid paramter or the like. This indicates a logic failure within the code. Depending on the level of failure, an exception may be appropriate, or forcing immediate termination may be preferable (as assert does). Generally, these situations indicate that something has broken somewhere in the code, and you effectively cannot trust anything else to be correct - there may be rampant memory corruption. Your ship is sinking, get off.

To paraphrase, exceptions are for when you have a problem you can deal with, but you can't deal with at the place you notice it. Problems you can't deal with should simply kill the program; problems you can deal with right away should simply be dealt with.

Unorganized answered 16/11, 2009 at 20:46 Comment(1)
You're answering the wrong question. We don't want to know why we should (or should not) consider using exceptions for handling error scenarios -- we want to know why we should (or should not) use them for non-error-handling scenarios.Maxiemaxilla
R
5

I read some of the answers here. I'm still amazed on what all this confusion is about. I strongly disagree with all this exceptions==spagetty code. With confusion I mean, that there are people, which don't appreciate C++ exception handling. I'm not certain how I learned about C++ exception handling -- but I understood the implications within minutes. This was around 1996 and I was using the borland C++ compiler for OS/2. I never had a problem to decide, when to use exceptions. I usually wrap fallible do-undo actions into C++ classes. Such do-undo actions include:

  • creating/destroying a system handle (for files, memory maps, WIN32 GUI handles, sockets, and so on)
  • setting/unsetting handlers
  • allocating/deallocating memory
  • claiming/releasing a mutex
  • incrementing/decrementing a reference count
  • showing/hiding a window

Than there are functional wrappers. To wrap system calls (which do not fall into the former category) into C++. E.g. read/write from/to a file. If something fails, an exception will be thrown, which contains full information about the error.

Then there is catching/rethrowing exceptions to add more information to a failure.

Overall C++ exception handling leads to more clean code. The amount of code is drasticly reduced. Finally one can use a constructor to allocate fallible resources and still maintain a corruption free environment after such a failure.

One can chain such classes into complex classes. Once a constructor of some member/base object is exectued, one can rely on that all other constructors of the same object (executed before) executed successfully.

Reimer answered 7/4, 2010 at 15:2 Comment(0)
E
3

Exceptions are a very unusual method of flow control compared to the traditional constructs (loops, ifs, functions, etc.) The normal control flow constructs (loops, ifs, function calls, etc.) can handle all the normal situations. If you find yourself reaching for an exception for a routine occurrence, then perhaps you need to consider how your code is structured.

But there are certain types of errors that cannot be handled easy with the normal constructs. Catastrophic failures (like resource allocation failure) can be detected at a low level but probably can't be handled there, so a simple if-statement is inadequate. These types of failures generally need to be handled at a much higher level (e.g., save the file, log the error, quit). Trying to report an error like this through traditional methods (like return values) is tedious and error-prone. Furthermore, it injects overhead into layers of mid-level APIs to handle this bizarre, unusual failure. The overhead distracts client of these APIs and requires them to worry about issues that are beyond their control. Exceptions provide a way to do non-local handling for big errors that's mostly invisible to all the layers between the detection of the problem and the handler for it.

If a client calls ParseInt with a string, and the string doesn't contain an integer, then the immediate caller probably cares about the error and knows what to do about it. So you'd design ParseInt to return a failure code for something like that.

On the other hand, if ParseInt fails because it couldn't allocate a buffer because memory is horribly fragmented, then the caller isn't going to know what to do about that. It would have to bubble this unusual error up and up to some layer that deals with these fundamental failures. That taxes everyone in between (because they have to accommodate the error passing mechanism in their own APIs). An exception makes it possible to skip over those layers (while still ensuring necessary clean-up happens).

When you're writing low-level code, it can be hard to decide when to use traditional methods and when to throw exceptions. The low-level code has to make the decision (throw or not). But it's the higher level code that truly knows what's expected and what's exceptional.

Epidiascope answered 16/11, 2009 at 19:21 Comment(7)
And yet, in Java, parseInt actually does throw an exception. So, I would say opinions about the appropriateness of throwing exceptions are highly dependent on pre-existing practices within the standard libraries of the their chosen development language.Colourable
Java's runtime has to keep track of information anyway, so that makes it easy for exceptions to be generated... c++ code tends to not have that information on hands, so it has a performance penalty to generate it. By not keeping it on hand, it tends to be faster/smaller/more cache friendly etc. Also, java code has dynamic checks on things like arrays etc, a feature not inherent in c++, so java extra class of exceptions which the developer is already taking care of a lot of these things with try/catch blocks, so why not use exceptons for EVERYTHING?Jewbaiting
@Charles - The question is tagged with C++, so I was answering from that perspective. I've never heard a Java (or C#) programmer say that exceptions are only for exceptional conditions. C++ programmers say this regularly, and the question was about why.Epidiascope
Actually, C# exceptions do say that as well, and the reason is that exceptions are very expensive to throw in .NET (more so than e.g. in Java).Doorkeeper
@Adrian: true, the question is tagged with C++, although exception handling philosophy is somewhat of a cross-language dialog, as languages certainly influence each other. Anyway, Boost.lexical_cast throws an exception. But is an invalid string really so "exceptional"? It's probably not, but the Boost developers thought that having that cool cast syntax was worth using exceptions. This just goes to show how subjective the whole thing is.Colourable
-1 sorry, you're answering the wrong question. We don't want to know why we should (or should not) consider using exceptions for handling error scenarios -- we want to know why we should (or should not) use them for non-error-handling scenarios.Maxiemaxilla
@Charles: Yes, it's subjective, which is the point I was trying to make in the final paragraph of my answer. @j_random_hacker: Yeah, I guess I misread the question or I answered before some revisions to it.Epidiascope
E
3

There's several reasons in C++.

First, it's frequently hard to see where exceptions are coming from (since they can be thrown from almost anything) and so the catch block is something of a COME FROM statement. It's worse than a GO TO, since in a GO TO you know where you're coming from (the statement, not some random function call) and where you're going (the label). They're basically a potentially resource-safe version of C's setjmp() and longjmp(), and nobody wants to use those.

Second, C++ doesn't have garbage collection built in, so C++ classes that own resources get rid of them in their destructors. Therefore, in C++ exception handling the system has to run all the destructors in scope. In languages with GC and no real constructors, like Java, throwing exceptions is a lot less burdensome.

Third, the C++ community, including Bjarne Stroustrup and the Standards Committee and various compiler writers, has been assuming that exceptions should be exceptional. In general, it's not worth going against language culture. The implementations are based on the assumption that exceptions will be rare. The better books treat exceptions as exceptional. Good source code uses few exceptions. Good C++ developers treat exceptions as exceptional. To go against that, you'd want a good reason, and all the reasons I see are on the side of keeping them exceptional.

Eschar answered 16/11, 2009 at 21:0 Comment(5)
"C++ doesn't have garbage collection built in, so C++ classes that own resources get rid of them in their destructors. Therefore, in C++ exception handling the system has to run all the destructors in scope. In languages with GC and no real constructors, like Java, throwing exceptions is a lot less burdensome." - destructors have to be run when leaving the scope normally, and Java finally blocks aren't any different from C++ destructors in terms of implementation overhead.Doorkeeper
I like this answer but, like Pavel I don't think the second point is legit. When the scope ends for any reason, including other types of error handling or just continuing the program, destructors would be called anyway.Emeric
+1 for mentioning language culture as the reason to go with the flow. That answer is unsatisfying to some, but it is a genuine reason (and I believe it's the most accurate reason).Maxiemaxilla
@Pavel: Please ignore my incorrect comment above (now deleted) saying that Java finally blocks are not guaranteed to be run -- of course they are. I was getting confused with the Java finalize() method, which is not guaranteed to be run.Maxiemaxilla
Looking back at this answer, the issue with destructors is that they all have to be called right then, rather than potentially being spaced out with each function return. That's still not a very good reason, but it has, I think, a little validity.Eschar
B
2

This is a bad example of using exceptions as control flow:

int getTotalIncome(int incomeType) {
   int totalIncome= 0;
   try {
      totalIncome= calculateIncomeAsTypeA();
   } catch (IncorrectIncomeTypeException& e) {
      totalIncome= calculateIncomeAsTypeB();
   }

   return totalIncome;
}

Which is very bad, but you should be writing:

int getTotalIncome(int incomeType) {
   int totalIncome= 0;
   if (incomeType == A) {
      totalIncome= calculateIncomeAsTypeA();
   } else if (incomeType == B) {
      totalIncome= calculateIncomeAsTypeB();
   }
   return totalIncome;
}

This second example obviously needs some refactoring (like using the design pattern strategy), but illustrates well that exceptions are not meant for control flow.

Exceptions also have some performance penalties associated, but performance problems should follow the rule: "premature optimization is the root of all evil"

Bersagliere answered 16/11, 2009 at 18:58 Comment(3)
Looks like a case when more polymorphism would be good, instead of checking incomeType.Coact
@Sarah yes I know it would be good. It's here for illustration purposes only.Bersagliere
Why is it bad? Why should you write it the 2nd way? The asker wants to know reasons for rules, not rules. -1.Maxiemaxilla
A
2
  1. Maintainability: As mentioned by people above, throwing exceptions at a drop of a hat is akin to using gotos.
  2. Interoperability: You can't interface C++ libraries with C/Python modules (atleast not easily) if you are using exceptions.
  3. Performance degradation: RTTI is used to actually find the type of the exception which imposes additional overhead. Thus exceptions are not suitable for handling commonly occurring use cases(user entered int instead of string etc).
Argybargy answered 16/11, 2009 at 19:7 Comment(7)
1. But in some languages exceptions are thrown at a drop of a hat. 3. Sometimes performance doesn't matter. (80% of the time?)Hapten
2. C++ programs almost always use exceptions, because so much of the standard library uses them. The container classes throw. The string class throws. The stream classes throw.Eschar
What container and string operations throw, except for at()? That said, any new can throw, so...Doorkeeper
RTTI is not strictly necessary for Try/Throw/Catch as far as I understand. Performance degradation always mentioned, and I believe it, but no one ever mentions a scale or links to any references.Emeric
UncleBens: (1) is for Maintainability not performance. Excessive use to try/catch/throw reduces the readability of the code. Rule of thumb(and I might come under fire for this but its just my opinion), lesser the entry and exit points, the easier it is to read the code. David Thornley: Not when you need interoperability. -fno-exceptions flag in gcc disables exceptions when you are compiling a library that is called from C code.Argybargy
Catskul: codeproject.com/KB/cpp/exceptionhandler.aspx explains the implementation. Easy to see why it degrades performance from that article.(See how the control flow of the function is modified due to exception, how the type of the exception is resolved etc)Argybargy
Catskul: codesourcery.com/public/cxx-abi/abi-eh.html#cxx-abi RTTI is used according to C++ standard.Argybargy
O
2

I would say that exceptions are a mechanism to get you out of current context (out of current stack frame in the simplest sense, but it's more than that) in a safe way. It's the closest thing structured programming got to a goto. To use exceptions in the way they were intended to be used, you have to have a situation when you can't continue what you're doing now, and you can't handle it at the point where you are now. So, for example, when user's password is wrong, you can continue by returning false. But if the UI subsystem reports that it can't even prompt the user, simply returning "login failed" would be wrong. The current level of code simply does not know what to do. So it uses an exception mechanism to delegate the responsibility to someone above who may know what to do.

Oeildeboeuf answered 16/11, 2009 at 20:13 Comment(0)
K
2

One very practical reason is that when debugging a program I often flip on First Chance Exceptions (Debug -> Exceptions) to debug an application. If there are a lot of exceptions happening it's very difficult to find where something has gone "wrong".

Also, it leads to some anti-patterns like the infamous "catch throw" and obfuscates the real problems. For more information on that see a blog post I made on the subject.

Kanchenjunga answered 16/11, 2009 at 23:6 Comment(0)
S
2

I prefer to use exceptions as little as possible. Exceptions force the developer to handle some condition that may or may not be a real error. The definition of whether the exception in question is a fatal problem or a problem that must be handled immediately.

The counter argument to that is it just requires lazy people to type more in order to shoot themselves in their feet.

Google's coding policy says to never use exceptions, especially in C++. Your application either isn't prepared to handle exceptions or it is. If it isn't, then the exception will probably propagate it up until your application dies.

It's never fun to find out some library you have used throws exceptions and you were not prepared to handle them.

Seeker answered 17/11, 2009 at 3:14 Comment(0)
S
1

Legitimate case to throw an exception:

  • You try to open a file, it's not there, a FileNotFoundException is thrown;

Illegitimate case:

  • You want to do something only if a file doesn't exist, you try to open the file, and then add some code to the catch block.

I use exceptions when I want to break the flow of the application up to a certain point. This point is where the catch(...) for that exception is. For example, it's very common that we have to process a load of projects, and each project should be processed independently of the others. So the loop that process the projects has a try...catch block, and if some exception is thrown during the project processing, everything is rolled back for that project, the error is logged, and the next project is processed. Life goes on.

I think you should use exceptions for things like a file that doesn't exist, an expression that is invalid, and similar stuff. You should not use exceptions for range testing/ data type testing/ file existence/ whatever else if there's an easy/ cheap alternative to it. You should not use exceptions for range testing/ data type testing/ file existence/ whatever else if there's an easy/ cheap alternative to it because this sort of logic makes the code hard to understand:

RecordIterator<MyObject> ri = createRecordIterator();
try {
   MyObject myobject = ri.next();
} catch(NoSuchElement exception) {
   // Object doesn't exist, will create it
}

This would be better:

RecordIterator<MyObject> ri = createRecordIterator();
if (ri.hasNext()) {
   // It exists! 
   MyObject myobject = ri.next();
} else {
   // Object doesn't exist, will create it
}

COMMENT ADDED TO THE ANSWER:

Maybe my example wasn't very good - the ri.next() should not throw an exception in the second example, and if it does, there's something really exceptional and some other action should be taken somewhere else. When the example 1 is heavily used, developers will catch a generic exception instead of the specific one and assume that the exception is due to the error that they're expecting, but it can be due to something else. In the end, this leads to real exceptions being ignored as exceptions became part of the application flow, and not an exception to it.

The comments on this may add more than my answer itself.

Sakhuja answered 16/11, 2009 at 20:27 Comment(4)
Why not? Why not use exceptions for the 2nd case you mention? The asker wants to know reasons for rules, not rules.Maxiemaxilla
Thanks for elaborating, but IMHO your 2 code snippets have almost identical complexity -- both use highly localised control logic. Where the complexity of exceptions most clearly exceeds that of it/then/else is when you have a bunch of statements inside the try block, any one of which could throw -- would you agree?Maxiemaxilla
Maybe my example wasn't very good - the ri.next() should not throw an exception in the second example, and if it does, there's something really exceptional and some other action should be taken somewhere else. When the example 1 is heavily used, developers will catch a generic exception instead of the specific one and assume that the exception is due to the error that they're expecting, but it can be due to something else. In the end, this leads to real exceptions being ignored as exceptions became part of the application flow, and not an exception to it.Sakhuja
So you're saying: Over time, other statements can accrete inside the try block and then you aren't certain any more that the catch block is really catching the thing you thought it was catching -- is that right? While it's harder to misuse the if/then/else approach in the same way because you can only test 1 thing at a time rather than a set of things at once, so the exceptiony approach can lead to more fragile code. If so please discuss this in your answer and I'll happily +1, as I think code fragility is a bona fide reason.Maxiemaxilla
C
0

Basically, exceptions are an unstructured and hard to understand form of flow control. This is necessary when dealing with error conditions that are not part of the normal program flow, to avoid having error handling logic clutter up the normal flow control of your code too much.

IMHO exceptions should be used when you want to provide a sane default in case the caller neglects to write error handling code, or if the error might best be handled further up the call stack than the immediate caller. The sane default is to exit the program with a reasonable diagnostic error message. The insane alternative is that the program limps along in an erroneous state and crashes or silently produces bad output at some later, harder to diagnose point. If the "error" is enough a normal part of program flow that the caller could not reasonably forget to check for it, then exceptions should not be used.

Conterminous answered 16/11, 2009 at 19:6 Comment(0)
B
0

I think, "use it rarely" ist not the right sentence. I would prefer "throw only in exceptional situations".

Many have explained, why exceptions should not used in normal situations. Exceptions have their right for error handling and purely for error handling.

I will focus on an other point:

An other thing is the performance issue. Compilers struggled long to get them fast. I am not sure, how the exact state is now, but when you use exceptions for control flow, than you will get an other trouble: Your program will become slow!

The reason is, that exceptions are not only very mighty goto-statements, they also have to unwind the stack for all the frames they leave. Thus implicitely also the objects on stack have to be deconstructed and so on. So without be aware of it, one single throw of an exception will really get a whole bunch of mechanics be involved. The processor will have to do a mighty lot.

So you will end up, elegantly burning your processor without knowing.

So: use exceptions only in exceptional cases -- Meaning: When real errors occured!

Blim answered 16/11, 2009 at 19:16 Comment(5)
"The reason is, that exceptions are not only very mighty goto-statements, they also have to unwind the stack for all the frames they leave. Thus implicitely also the objects on stack have to be deconstructed and so on." - if you're down several stack frames on the callstack, then all those stack frames (and objects on them) will have to be destructed eventually anyway - it doesn't matter if that happens because you return normally, or because you throw an exception. In the end, short of calling std::terminate(), you still have to destruct.Doorkeeper
You are of course right. But still please remember: Every time you use exceptions, the system has to provide the infrastructure to do all this magically. I just wanted to slightly make clear, that it is not only a goto. Also on unwinding, the system has to find the right catching place, what will cost extra time, code and the use of RTTI. So it is much more than a jump -- most people just don't know comprehend this.Blim
So long as you stick to standard-compliant C++, RTTI is unavoidable. Otherwise, of course overhead is there. It's just that I often see it significantly exaggerated.Doorkeeper
-1. "Exceptions have their right for error handling and purely for error handling" -- says who? Why? Performance cannot be the only reason. (A) Most code in the world is not in the innermost loop of a game rendering engine or matrix multiplication function, so using exceptions will have no perceptible performance difference. (B) As onebyone noted in a comment somewhere, all that stack unwinding would ultimately need to take place anyway even if the old C-style check-for-error-return-values-and-pass-back-if-needed error-handling approach was used.Maxiemaxilla
Say I. In this thread there where plenty of reasons cited. Just read them. I wanted to describe only one. If it is not the one you needed, don't blame me.Blim
H
0

The purpose of exceptions is to make software fault tolerant. However having to provide a response to every exception thrown by a function leads to suppression. Exceptions are just a formal structure forcing programmers to acknowledge that certain things can go wrong with a routine and that the client programmer needs to be aware of these conditions and cater for them as necessary.

To be honest, exceptions are a kludge added to programming languages to provide developers with some formal requirement that shifts the responsibility of handling error cases from the immediate developer to some future developer.

I believe that a good programming language does not support exceptions as we know them in C++ and Java. You should opt for programming languages that can provide alternative flow for all sorts of return values from functions. The programmer should be responsible for anticipating all forms of outputs of a routine and handle them in a seperate code file if I could have my way.

Heptastich answered 16/11, 2009 at 20:33 Comment(0)
P
0

I use exceptions if:

  • an error occured that cannot be recovered from locally AND
  • if the error is not recovered from the program should terminate.

If the error can be recovered from (the user entered "apple" instead of a number) then recover (ask for the input again, change to default value, etc.).

If the error cannot be recovered from locally but the application can continue (the user tried to open a file but the file does not exist) then an error code is appropriate.

If the error cannot be recovered from locally and the application cannot continue without recovering (you are out of memory/disk space/etc.), then an exception is the right way to go.

Parquetry answered 16/11, 2009 at 23:22 Comment(4)
-1. Please read the question carefully. Most programmers either think that exceptions are appropriate for certain types of error handling, or that they are never appropriate -- they don't even consider the possibility of using them for other, more exotic forms of flow control. The question is: why is that?Maxiemaxilla
You should also read it carefully. I answered "What is the philosophy behind being exceptionally conservative with how they are used?" with my philosophy behind being conservative with how they are used.Parquetry
IMHO you haven't explained why the conservatism is necessary. Why are they only "appropriate" sometimes? Why not all the time? (BTW I think your suggested approach is just fine, it's more or less what I do myself, I just don't think it gets at much of the why of the question.)Maxiemaxilla
The OP asked seven distinct questions. I chose to answer only one. I'm sorry you feel that is worth a down-vote.Parquetry
C
0

Who said they should be used conservatively ? Just never use exceptions for flow control and thats it. And who cares about the cost of exception when it already thrown ?

Contagious answered 22/11, 2009 at 0:38 Comment(0)
S
0

My two cents:

I like to use exceptions, because it allows me to program as if no errors will happen. So my code remains readable, not scattered with all kinds of error-handling. Of course, the error handling (exception handling) is moved to the end (the catch block) or is considered the responsability of the calling level.

A great example for me, is either file handling, or database handling. Presume everything is ok, and close your file at the end or if some exception occurs. Or rollback your transaction when an exception occurred.

The problem with exceptions, is that it quickly gets very verbose. While it was meant to allow your code to remain very readable, and just focus on the normal flow of things, but if used consistently almost every function call needs to be wrapped in a try/catch block, and it starts to defeat the purpose.

For a ParseInt as mentioned before, i like the idea of exceptions. Just return the value. If the parameter was not parseable, throw an exception. It makes your code cleaner on the one hand. At the calling level, you need to do something like

try 
{
   b = ParseInt(some_read_string);
} 
catch (ParseIntException &e)
{
   // use some default value instead
   b = 0;
}

The code is clean. When i get ParseInt like this scattered all over, i make wrapper functions that handle the exceptions and return me default values. E.g.

int ParseIntWithDefault(String stringToConvert, int default_value=0)
{
   int result = default_value;
   try
   {
     result = ParseInt(stringToConvert);
   }
   catch (ParseIntException &e) {}

   return result;
}

So to conclude: what i missed througout the discussion was the fact that exceptions allow me to make my code easier/more readable because i can ignore the error conditions more. Problems:

  • the exceptions still need to be handled somewhere. Extra problem: c++ does not have the syntax that allows it to specify which exceptions a function might throw (like java does). So the calling level is not aware which exceptions might need to be handled.
  • sometimes code can get very verbose, if every function needs to be wrapped in a try/catch block. But sometimes this still makes sense.

So that makes it hard to find a good balance sometimes.

Substratosphere answered 22/11, 2009 at 17:11 Comment(0)
M
-1

I'm sorry but the answer is "they are called exceptions for a reason." That explanation is a "rule of thumb". You can't give a complete set of circumstances under which exceptions should or should not be used because what a fatal exception (English definition) is for one problem domain is normal operating procedure for a different problem domain. Rules of thumb are not designed to be followed blindly. Instead they are designed to guide your investigation of a solution. "They are called exceptions for a reason" tells you that you should determine ahead of time what is a normal error the caller can handle and what is an unusual circumstance the caller cannot handle without special coding (catch blocks).

Just about every rule of programming is really a guideline saying "Don't do this unless you have a really good reason": "Never use goto", "Avoid global variables", "Regular expressions pre-increment your number of problems by one", etc. Exceptions are no exception....

Myrmecophagous answered 17/11, 2009 at 2:58 Comment(5)
... and the asker would like to know why it is a rule of thumb, rather than hear (yet again) that it's a rule of thumb. -1.Maxiemaxilla
I acknowledged that in my response. There is no explicit why. Rules of thumb are vague by definition. If there were an explicit why, it would be a rule and not a rule of thumb. Every explanation in every other answer above contains caveats so they don't explain why either.Myrmecophagous
There may be no definitive "why", but there are partial "why"s that others mention, e.g. "because that's what everyone else is doing" (IMHO a sad but real reason) or "performance" (IMHO this reason is usually overstated, but it's a reason nonetheless). The same is true of the other rules of thumb like avoiding goto (usually, it complicates control flow analysis more than an equivalent loop) and avoiding global variables (they potentially introduce a lot of coupling, making later code changes difficult, and usually the same goals can be achieved with less coupling other ways).Maxiemaxilla
And all those whys have long lists of caveats which was my answer. There is no real why beyond broad experience in programming. There are some rules of thumb that go beyond why. The same rule of thumb can cause "experts" to disagree on why. You yourself take "performance" with a grain of salt. That would be my top of list. Flow control analysis doesn't even register with me because (like "no goto") I find flow control issues overstated. I'll also point out that my answer attempts to explain HOW you use the "trite" answer.Myrmecophagous
I agree with you as far as all rules of thumb have long lists of caveats. Where I disagree is that I think it's worthwhile trying to identify the original reasons for the rule, as well as the specific caveats. (I mean in an ideal world, where we have infinite time to ponder these things and no deadlines of course ;)) I think that's what the OP was asking for.Maxiemaxilla

© 2022 - 2024 — McMap. All rights reserved.