9 ms·
> Root cause > Traditional C++ exceptions have two main problems: > 1) the exceptions are allocated in dynamic memory because of inheritance and because of no
by jmyeet 5y ago
> Root cause
> Traditional C++ exceptions have two main problems:
> 1) the exceptions are allocated in dynamic memory because of inheritance and because of non-local constructs like std::current_exception. This prevents basic optimizations like transforming a throw into a goto, because other parts of the program should be able to see that dynamically allocated exception object. And it causes problems with throwing exceptions in out-of-memory situations.
> 2) exception unwinding is effectively single-threaded, because the table driven unwinder logic used by modern C++ compilers grabs a global mutex to protect the tables from concurrent changes. This has disastrous consequences for high core counts and makes exceptions nearly unusable on such machines.
That's really interesting. I've become very anti-exceptions in recent years far various much-discussed reasons (eg hard to follow, false economy, difficult if not impossible to write threadsafe C++ code in particular, use of exceptions as flow control is an anti-pattern).
One of the porposals is a value-or-error type object, which is basically what Rust has. I really like Rust's enums and match expressions.
It seems so difficult to make changes like this to C++ at this point, at what point do you just have to start again?
- nly 5y agostd::expected doesn't require language changes, it's a library type. If anything it shows how C++ is multi-paradigm
- ModernMech 5y agoThis is the great strength and weakness of C++. Increasingly the answer to C++'s rough edges is "We don't do things that way anymore. Everyone does X now", where X is the hot new thing. RAII is the best example I can think of, where some people insist that no one would ever use the "new" and "delete" keywords anymore. Except for all the C++ devs that do, and all the C++ code that exists that does and must be maintained. It leads to the current situation where you have C++ "the language" which is everything, and then C++ "the subset that everyone uses" where that subset constantly changes with time and development context.
- pjmlp 5y agoMy first contact with RAII was in 1993 with Turbo C++ 1.0, it is hardly a hot new thing.
- ModernMech 5y agoThat's not my point. The point is that this feature once was the hot new thing, and in the future there will be a hot new way to do the same thing in addition to all the old ways, because that's how C++ evolves. And I would have to say the average C++ dev did not know about RAII in 1993.
- KerrAvon 5y agoFor the sake of pedantry only: I don't think we called it RAII in 1993-1996, but the technique was in use in that period, though it wasn't standardized in any way. IIRC, Mac developers would have been widely exposed to it by Metrowerks PowerPlant during that time.
- ncmncm 5y agoDestructors have been in C++ since the mid-80s. Anybody who did not know about RAII in 1993 did not know any C++.
- duped 5y agoI think I agree with the sentiment but not the example, no one seriously advocates for no-new/no-delete (collections must still be written, somewhere) but rather that new/delete are generally code smell and there are idioms that can help isolate bugs. Part of maintaining old code is updating to those idioms. But yea this kind of thing hit me recently on the interview circuit. I wrote some correct C++ (in that it was correct for the idioms in vogue when I last wrote C++ regularly for money) but I got feedback I wasn't as senior as they hoped due to my (lack of) C++ knowledge. Part of that was a shitty interviewer, but it's also just a fundamental part of the language. If you leave for a few years or try and change shops you find that everything under you has been removed or a completely different subset of the language is being used elsewhere. The complete lack of an ecosystem just reinforces that.
- fbkr 5y agoI've been using `expected`, i.e. value-or-error type, for a while in C++ and it works just fine, but the article shows it has some noticeable overhead for the `fib` workload for instance. Not sure if the Rust implementation has a different design to make it perform better though.
- masklinn 5y ago> Not sure if the Rust implementation has a different design to make it perform better though. Prolly not, I expect the issue comes from the increase in branches since a value-based error reporting has to branch on every function return. Even if the branch is predictible, it’s not free. And fib() would be a worst-case scenario as it does very little per-call, the constant per-call overhead would be rather major.
- ncmncm 5y agoUsing up scarce branch-prediction slots is a good way to make your program unoptimizable. Time wasted because you ran out will not show up anywhere localized on your profile. (Likewise blowing any other cache.)
- astrange 5y agoUsing up BTB slots is an interesting problem but in practice doesn't seem to be a big issue. If it was, ISAs would use things like hinted branches but instead they've been taking them away. Code size is more important but hot/cold splitting can help there. A problem with using exceptions instead is they defeat the return address prediction by unwinding the stack.
- ncmncm 5y agoThat hinted branches are not useful tells us nothing about the importance of branch predictor footprint. When hinting branches gives you a bigger L1 cache footprint, it has a high cost. Compilers nowadays use code motion to implement branch hinting, which does not burn L1 cache. (Maybe code motion is what you mean by "hot/cold splitting"?) Anyway the hint we really need, no ISA has: "do not predict this branch". (We approximate that with constructs that generate a "cmov" instruction, which anyway is not going away.) How does using exceptions defeat return address prediction? You are explicitly not returning, so any prediction would be wrong anyway. In the common case, you do return, and the predictor works fine.
- jeffbee 5y agoYou don't have to start over with the whole language. Just use -fno-exceptions in your project and dictate the use of std::optional, or absl::StatusOr, or whatever your favorite variant return type may be. For the examples in the article, it may be perfectly fine to not support failure, to simply std::abort whenever the sqrt of a non-positive is requested and rename the function sqrt_or_die.
- jmyeet 5y agoExcept if that's incompatible with libraries you're using. For the record, I like absl::StatusOr<>.
- kevin_thibedeau 5y agoYou also have to abandon operator new and STL unless you want to pretend they never fail.
- pjmlp 5y agoJust use the no_throw variant, no need to abandon new.
- jeffbee 5y agoRight, I use an allocator that just aborts. Imagining your program can recover from alloc failures has always struck me as fanciful, or at least out of the realm of my experience.
- Inityx 5y agoIt's a legitimate thing in embedded and other memory-constrained circumstances, when you have something like a large cache and an allocation failure can trigger manual pruning or GC.
- jkaptur 5y agoI'm relatively new to C++ and I found absl::StatusOr<T> and its helper macros to be extraordinarily pleasant.
- kirbyfan64sos 5y agoIt has helper macros now? I liked StatusOr but had to write my own helper macros for it at the time.
- trimbo 5y agoEx: https://google.github.io/or-tools/cpp/status__macros_8h_source.html https://google.github.io/or-tools/cpp/status__macros_8h_sour...
- alskdjflaskjdhf 5y agoYep, these are used extensively at Google (which is where the abseil library came from, and which is famously anti-cpp-exceptions) and they work very well. If I somehow found myself writing a new C++ project I'd probably reach for abseil (and some of the other parts of the Google toolchain: GoogleTest for testing, bazel for builds).
- deleted 5y ago[deleted]
- layer8 5y agoRegarding maintainability ("hard-to-follow"), I’ve become a big fan of Java’s checked exceptions (and also their causal chaining and adding of "suppressed" exceptions, which would be nice to have for C++ destructors). I effectively see them as a sum type together with the return type, just using different syntax. It’s an important reason why I stick to the language, because no other language has that kind of statically-typed exceptions. As the article explains, the problems in C++ are more an ABI issue than a programming language issue (except for the by-reference vs. by-value semantics). You could implement exceptions internally by variant-like return values, for example, similar to how error passing is done in Rust, while still having it look like exceptions on the language level. It would be fun for future languages and runtimes to more easily be able to switch the underlying mechanism, or possibly to be able to use different implementation mechanisms for different parts of a program as needed.
- dtech 5y agoJava's checked exceptions are generally regarded as a mistake. There's a reason no other languages has them, and newer JVM languages (Groovy, Clojure, Scala, Kotlin) treat all exceptions as runtime. Anders Hejlsberg (creator of Delphi, C# and Typescript) also has an excellent article on their problems [1]. In modern Java I see nearly only runtime exceptions used, especially because that's necessary for most Java 8+ lambda API's. Especially when used as an error ADT they're awful because they mix everything from "you have to handle this 90% of the time" to "the universe physics just changed, sorry" into one construct. Much better to use something like Vavr's Try and explicitly propagate the error as a value. [1] https://www.artima.com/articles/the-trouble-with-checked-exceptions https://www.artima.com/articles/the-trouble-with-checked-exc...
- pjmlp 5y ago> There's a reason no other languages has them, As always someone is wrong on Internet. Java adopted a feature that was initially introduced in CLU, adopted by Mesa/Cedar, Modula-2+ and Modula-3, was being considered for ongoing ISO C++ standardization at the time.
- 5y ago