The alternative to having the exception-handling code out of line is, of course, handling all exceptional conditions in line, which not only makes the code harder to read but makes the generated object code bigger and, if it fails to fit in a cache line or causes unwholesomely large numbers of branch evaluations (see spectre and meltdown) results in slower or more insecure code. The real answer to C++ exceptions is the classic C-style programming where all errors are just ignored.
Of course, no amount of reasoning or hard data survives in the face of religious belief, and 80% of programmers out there are part of one cargo cult or another, so carry on with what you were doing. It's probably good enough.
That's a mischaracterization of most C code written by professionals, because (with appropriate compiler flags) return codes have to be explicitly ignored. Contrast with languages where most functions don't even have a return code, and the compiler/interpreter doesn't complain about failing to catch possible exceptions. I see this a lot in Python code (which I generally like and have liked since 1.5 BTW), a bit less so in C++, etc. Even "elite" programmers in those languages tend to be sloppy that way, which I rarely see in C programmers with more than a couple of years' experience.
I've been a professional programmer for 40 years, much of that in C. I've seen a lot of code, some of it in production in critical systems for many years. It is not a mischaracterization, it is a description of the state of the art.
No, Python's default behavior is an uncaught exception causing program termination with a stack trace, which is not generally what anyone wants. You have to add code to get non-default behavior.
> A stack of ten function calls should not have ten try blocks
While I agree with that, it's kind of missing the point. Exceptions can be used well. So can return codes. The problem is that when letting an error/exception be "someone else's problem" is the default, that tends to be what everyone does. It becomes nobody's problem, except the user who's left staring at an inscrutable program-termination message. It's the Volunteers' Dilemma in code.
A good error-handling paradigm would require that errors be explicitly handled, passed on, or suppressed. Exceptions let them be invisible. C-style error returns are a bit cumbersome, but still better for correctness. My favorite approach right now is Zig's, based on error returns but with extra features (e.g. defer and try) to make common idioms less cumbersome. I've heard Rust is similar, but haven't really looked into it.
We narrowed it down to the fact that the compiler wasn't free to A) inline the function, B) reorder internal operations in the function which came before vs. after the exception being thrown.
IIRC that was the originally proposed behavior for noexcept, but it was changed at some point to call std::terminate instead.
However, I don't find any referenc to noexcept having gotten defined behaviour. Do you have any pointers?
> Whenever an exception is thrown and the search for a handler (14.4) encounters the outermost block of a function with a non-throwing exception specification, the function std::terminate is called (14.6.1).
I don't know when precisely noexcept was changed to call std::terminate, but I did find N3103 (included in the 2010-08 post-Rapperswil mailing [1]), which argued that the standard should require calling std::terminate to avoid security issues.
[0]: https://github.com/cplusplus/draft [1]: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n310...
I haven't measured the exact effect of this, but - although that if the wind doesn't throw the ship doesn't slow - having an exception-al path through the code will stop some compiler optimizations. I have seen this first hand but I also remember Walter Bright mentioning it either here or elsewhere.
If this is definitely the case maybe someone could chime in why exactly (I've never fiddled with a backend in the right places to deal with exceptions)
I wrote some tests, not sure if they are perfect:
#include <stdexcept>
volatile int i = 1;
__attribute__((noinline)) int test() {
return i;
}
int main()
{
if (test() == 0) throw std::runtime_error("");
return 0;
}
====>
Instruction count: 612
Memory usage: 116 KB, top: 116 KB
Compile time: 745326 micros
Execution time: 149 micros
Binary size: 98 KBThe above is with exception, below is without:
volatile int i = 1;
__attribute__((noinline)) int test() {
return i;
}
int main()
{
if (test() == 0) return -1;
return 0;
}
====>
Instruction count: 251
Memory usage: 16 KB, top: 16 KB
Compile time: 441626 micros
Execution time: 53 micros
Binary size: 3 KBJust remember to pick newlib as the target, as that is the thinnest, and the no-exceptions variant will be only 16kb.
Also, what are your compile flags? godbolt shows a pretty big difference with -O2.
I agree completely, it really does run everything from start to finish. Sometimes that's what you want, and sometimes you don't. I could break on main as an option, and then start the benchmarking after main. It would include teardown, unless you call _exit() at the end of main.
The compile flags are -O2, no linker GC.