Food for thought: what are the expected consequences of the exception?
If the error will stop the program flow and show a warning dialog to the user, it's useless to think too much about performance. More or less the same if it's going to log some message and abort the operation.
What is usually frowned upon is using the exception as a kind of goto for normal flow of the program. Exceptions should be... the exception.
Otherwise all this performance brouhaha is a waste of time.
Exception is a way to return from a function abnormally. So yes, they are for exceptional conditions. IMO it's perfectly fine to use them for input validation. The Java standard library itself does it all the time.
I think a language's standard library sets a good pattern for how to write idiomatic code in that language.
C++ throws on memory allocation error, but that's about it. Memory allocation errors are special in all languages. Because of lazy allocation and overcommit, unless you specifically set your environment to work otherwise, your program will probably just crash when it gets its first page fault that can't be honored.
Open a file? fstream sets .is_open() (or its operator bool, so just "if (!f)")
Write fails? Sets .fail()
POSIX stuff usually return an error code, and set errno.
Modern C++ has std::optional.
But there's of course another answer to this, and that is "C++ has zero cost abstractions", meaning for example if you don't check for nullptr, then neither will the language. There's no NullPointerException because C++ just says that this is Undefined Behavior.
Oh, here's one: If you use dynamic_cast to try to downcast into the wrong type, that'll throw an exception. But first of all: don't downcast, and second of all: This is not an "unexpected input to function". This is a complete programming error and it's probably best to terminate. I.e. this is something Go style would panic about, not return an error.
Do you have more specific examples about unexpected input to a function that you would want to return an error for?
Ugh. Actually std::stoi() violates this pattern. If std::optional existed in C++11 it would probably have been used here.
> Memory allocation errors are special in all languages.
Actually no, not in Java! You can catch java.lang.OutOfMemoryError just like any other exception. If the memory pressure is high though, it's possible that another OOM would be thrown from the code that handles it.
You can catch std::bad_alloc in C++, too. That's not my point. Especially because destructors free immediately (not wait for GC) I would expect C++ to handle this as a language much better than Java.
But when memory pressure is high you can get killed at any time. E.g. on Linux the OOM killer might decide that it's best for the system that your process dies, even if you've not done memory allocations or needed to page fault for hours.
IIRC OpenBSD doesn't overcommit memory, but in my experience its system stability is much worse when memory is low.
If you return an error, it needs to be checked for in every place where this function is called. Yes, I know C libraries and OS APIs do this often, but that's C, it's the only thing it can do. This just invites human error. Besides, it's often desirable to handle multiple different error conditions (arising from different steps as you process the input data) in one place, which is complicated with this approach. Java-like exception handling makes it easy to handle all errors in a single place and be sure none go unnoticed. This is especially useful when you're accepting external input (a file, a network packet/stream, an HTTP request, etc).
If you exit the program on error, this does work in some cases like command-line utilities, but your users would not be happy if your GUI app crashes when you open a malformed file.
Entire books, I'm sure, have been written about the pro and cons of exceptions for error handling.
Yes, I called it "idiomatic C++" before, but reasonable people disagree about the best option.
Smarter people than me have written good things in the E section of the C++ Core Guidelines. The people involved have overlap with the C++ standards committee:
In modern Java, and by modern I mean 8 and newer, you no longer need "finally", there's "try with resources" instead that would close everything upon leaving the try block:
try(FileInputStream in=new FileInputStream(file)){
// do something with the file data
}catch(IOException x){
x.printStackTrace();
}
It's been a very long time since I last wrote "something.close()".
I'm not arguing for or against this approach, merely responding to a question with a factual answer.
But since you brought it up ;) I bounce back and forth between which approach I like. Sometimes exceptions seem bulky and unwieldy and honestly a bit lazy. Returning an error feels verbose and annoying and bulky as well. But it also feels like returning an error forces you to think about what should happen, where exceptions let you kick the can down the road.
Neither are great, but I find that code that uses exceptions ends up being poorer in design and functioning, but also errors-on-return tends to be harder to read.
This is fine, this is exception handling which you are describing. What exceptions should not be for is handling normal control flow. That is what branches are for. That is what the article is about and what people replying are complaining about.
The problem is exceptions have entirely opaque flow control. They're the opposite of a goto statement: a comes-from statement if you will. Flow control could originate literally anywhere down the stack and that makes reasoning about what's happening very difficult. Depending on the language it can also have a super broad and ever-changing surface area.
This is not correct, a “comes-from” statement would still be the same behavior as a goto except it’s defined at the label and there is no “goto” at the departure line.
Exceptions are just “goto whatever catch is in the call stack”.
It’s still completely obvious when you see a throw statement that it can throw, and static analysis can tell you exactly what can be thrown by each function.
And it is still opaque where it goes to the programmer when they see it in code. The fact you can use tools to find where it might land (yeah, no shit, you can do same for goto...) is just a mitigation to the problem.
No, it is not the same problem. You need to trace every function, and every function calling those functions, and every function calling those functions, all the way to catch.
You can't "just" search for function name, you have to rely on code analysis tools.
Making code more opaque coz you can get thru the mess of it via tooling is terrible direction
Return only ever moves up the stack one level. That makes it really easy to reason about. This can move up the stack an unlimited amount, and the handler has to be prepared to properly deal with the current state, no matter where it comes from.
There's nothing opaque about having an implicit potential return after every line as well as an implicit additional error result value. Think Go, but without the ceremony.
Unchecked exceptions need extra care. I've seen process level exceptions being logged when the underlying cause was a failure to parse an integer. The exception was being caught in an exception handler half a dozen scopes away.
If the error will stop the program flow and show a warning dialog to the user, it's useless to think too much about performance. More or less the same if it's going to log some message and abort the operation.
What is usually frowned upon is using the exception as a kind of goto for normal flow of the program. Exceptions should be... the exception.
Otherwise all this performance brouhaha is a waste of time.