[HN Gopher] Current hardware trends make C++ exceptions harder t...
       ___________________________________________________________________
        
       Current hardware trends make C++ exceptions harder to justify
        
       Author : ingve
       Score  : 206 points
       Date   : 2022-02-22 17:33 UTC (5 hours ago)
        
 (HTM) web link (www.open-std.org)
 (TXT) w3m dump (www.open-std.org)
        
       | fpoling wrote:
       | Another problematic area is RTTI and dynamic_cast. Chromium
       | disables both that and exceptions. As a replacement in few places
       | where dynamic_cast can be useful Chromium adds a virtual function
       | to the base class to return the subclass or null.
       | 
       | Chromium code replaces exceptions with boolean flags and logging
       | or code to kill the current process for bad cases like out-of-
       | bound access.
        
       | erwincoumans wrote:
       | I always avoid C++ exceptions, and also try to avoid dynamic
       | memory allocations, smart pointers and RTTI etc whenever
       | possible. This is pretty common in latency and (pseudo) real-time
       | performance critical work, such as robotics control and 3d
       | gaming.
        
         | mhh__ wrote:
         | Memory allocation is the big think that new players often miss.
         | 
         | A lot of time I see people saying D is _unusable_ (excitedly)
         | for a certain usecase, and you know what if you use the GC a
         | lot it might be, but then I then see them write code that uses
         | raw malloc and free willy nilly. GC can bite you in the ass,
         | but so will basically all memory allocation if you don 't
         | understand the real trends of your program i.e. "Nature is a
         | language, can't you read?"
        
         | CyberRabbi wrote:
         | > (pseudo) real-time performance critical work, such as
         | robotics control and 3d gaming.
         | 
         | In reality all interactive applications are real-time
         | performance critical. Any normal user would say that an
         | unresponsive application is unacceptable. Sadly the entire
         | stack of our contemporary desktop runtime environments grew out
         | of background batch processing systems. This means that an
         | application becoming unresponsive can be a sign of normal
         | system operation.
         | 
         | Even if you avoid malloc, any memory access can trigger
         | swapping which can block your main thread indeterminately. Or
         | if you are running a high enough amount of CPU-hungry processes
         | concurrently, your main thread can block indeterminately.
         | 
         | Millions of programmers carry on building applications for
         | these systems blissfully unaware of these fundamental flaws.
         | Application runtimes like Electron flourish, riddled with
         | thousands of unbounded operations on every mouse click.
        
           | com2kid wrote:
           | > Even if you avoid malloc,
           | 
           | Way too many people believe the difference between
           | malloc/new+free/delete vs a GC is that one is deterministic
           | and the other isn't.
           | 
           | (It doesn't help that textbooks still teach this!)
           | 
           | Both are subject to the whims of the system's memory and, if
           | swap is enabled, IO systems.
           | 
           | And unless you understand your call graph very well,
           | constructing an object that constructs other objects is not
           | something you are likely to be capable of calculating the
           | performance of in a non-GC language.
           | 
           | Same goes for destructors.
           | 
           | The _actual_ difference is that in language without GC 's
           | typically have explicit syntax for dynamic memory allocation
           | (though it can happen by surprise in C++ from time to time!),
           | which means if you are writing latency sensitive code, you
           | can just avoid dynamic allocation altogether.
           | 
           | But there is no reason why GC languages can't do the same! In
           | fact, newer GC languages sometimes do, and nowadays C# give
           | you more control over stack allocation, so careful C# coding
           | can also avoid using dynamic memory.
        
         | worik wrote:
         | Why do you use C++ in those domains?
         | 
         | Curious as to the decision criteria.
        
           | cyber_kinetist wrote:
           | If you need any sort of numerical programming, C++ is
           | basically unmatched. It's fast and efficient (have total
           | control over memory and heap allocations), while able to
           | create complex math abstractions thanks to its template
           | system and operator overloading.
           | 
           | For example, Eigen is the only library (not in C++, but in
           | the entire programming space) that can optimize your math
           | expressions at compile-time. You can also perform techniques
           | like auto-differentiation by using templates. Meanwhile, Rust
           | still doesn't have full const generics, which are needed to
           | create a math library that's both efficient and easy to use
           | (nalgebra still depends on typenum, which is much uglier than
           | even the most esoteric C++ template stuff you can find!)
        
             | mhh__ wrote:
             | The D library "Mir GLAS" also does the eigen style
             | optimizations, IIRC.
             | 
             | It's not unique to C++ but yes Eigen is by far and away the
             | most mature option in that space. D templates are
             | significantly better than C++ ones in basically every way,
             | but the work is already done in C++ so I can't be too
             | boastful
        
             | erwincoumans wrote:
             | Agreed. Eigen can be relatively slow: in our Tiny
             | Differentiable Simulator, with C++ templatized code, we
             | generate C code using CppAdCodegen (by tracing a sim step)
             | which is easily 5 times faster than the original Eigen
             | code. And together with openmp it runs very fast: Ant
             | OpenAI gym simulation at 2 million steps per second on an
             | AMD Ryzen 3900x at 12 cores/24 threads).
             | 
             | See the (unreadable) generated code from Eigen+CppAd
             | Codegen here: https://github.com/google-research/tiny-
             | differentiable-simul...
        
           | DoctorDabadedoo wrote:
           | Ecosystem and performance.
           | 
           | There are other as performant languages out there that have a
           | "small" footprint (C, Rust, Zig, etc.), but the combo with
           | the libraries heavily used in robotics, mostly for image and
           | mathematics computation, makes it hard to look away from it:
           | C++ standard library, OpenCV, Eigen, Boost, PCL, ROS.
           | 
           | There may come a day when there is enough momentum and
           | support to look elsewhere, but right now C++ is king in this
           | domain. There is a lot of Python going on too, but usually
           | not for the same type of things.
        
           | erwincoumans wrote:
           | C is fine too. I pick a C++ subset that is closer to C than
           | to full modern C++17. Full control over memory access,
           | hardware access and execution. I haven't learned enough Rust
           | yet, that could be fine too, but Rust lacks still many
           | libraries in Robotics and Game development.
           | 
           | What else do you suggest?
        
             | ncmncm wrote:
             | In other words, you pick exclusively the worst features of
             | C++, and ignore all the actually useful, helpful features
             | that enable you to write better code. If you just want C,
             | use C.
        
               | Koshkin wrote:
               | Well, with a C++ compiler there's always hope that they
               | will pick up the good parts, eventually.
        
               | TillE wrote:
               | "Modern C++ bad" is such a weird HN meme, fortunately
               | it's not an opinion I see a lot elsewhere. Old C++ was
               | awful, the new stuff makes it actually usable.
               | 
               | Personally I like how even in embedded code where you
               | don't want to include the standard library, you can still
               | efficiently use language constructs like constexpr,
               | lambdas, etc.
        
               | Koshkin wrote:
               | Careful with lambdas, they are not without an overhead.
        
               | zozbot234 wrote:
               | Lambda is designed to be a zero-overhead feature. In the
               | worst case, it's the same as passing an additional void*
               | ctx argument to a plain old function, which is the most
               | common pattern in C/pre-lambda C++.
        
               | erwincoumans wrote:
               | I didn't mean to say "modern C++ is bad", but I'd prefer
               | a small subset that I can be sure no surprises happen: no
               | hidden memory allocations and such. In fact, we use
               | modern C++ and Eigen and CppAd Codegen in some of our
               | projects: the generated C code by tracing a full sim step
               | is about 5 times faster, likely due to no memory
               | allocations and cache friendly memory access.
        
           | addaon wrote:
           | I work in the same domains, plus hard embedded real-time.
           | Some projects end up choosing C, some C++. C has the
           | advantage of simplicity and more mature tooling, C++ has the
           | advantage of a stronger type system (able to move some things
           | from correct-by-testing to correct-by-construction) and a
           | medium-weight code generation system in the form of
           | templates.
        
       | RcouF1uZ4gsC wrote:
       | > It breaks the existing ABI, and all shared libraries would have
       | to be compiled with the new model, as otherwise unwinding breaks.
       | 
       | I am convinced that maintaining ABI is a huge technical debt to
       | C++. And that is largely occasioned by shared libraries.
       | 
       | Rust IMO made a fantastic decision to emphasize source
       | distribution (and making it super easy via cargo and crates).
        
         | synergy20 wrote:
         | you do pay the compilation time price though, sometimes it
         | takes really a long time to rebuild.
        
         | zozbot234 wrote:
         | Rust just punts to the C ABI for shared libraries. Which is
         | sensible since the whole point of those is to share code
         | system-wide, and the C ABI is the de-facto common interface for
         | "foreign" code on systems where shared libraries are widely
         | deployed.
        
       | Someone wrote:
       | FTA: The root cause is that the unwinder grabs a global mutex to
       | protect the unwinding tables from concurrent changes from shared
       | libraries
       | 
       | Is it unavoidable that that hurts performance badly for the
       | common case? I would think such concurrent changes are rare, so
       | if there's an asymmetric way to protect against that that's
       | faster in the happy path exists, that would be a big improvement.
       | 
       | https://en.wikipedia.org/wiki/Readers-writer_lock mentions _Read-
       | preferring RW locks_ , but (from cursory reading) doesn't say how
       | much that can help.
        
         | superjan wrote:
         | It is avoidable by using locks like you mention, the article
         | actually says so, but it causes an ABI change. They go on to
         | say that this makes it undesirable, but compared to what
         | alternative exactly?
        
         | chippiewill wrote:
         | The article discusses this.
         | 
         | RW lock is an option that helps but involves breaking the ABI
         | of EVERY shared library.
        
       | asveikau wrote:
       | > 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.
       | 
       | > The second problem could potentially be fixed by a
       | sophisticated implementation, but that would definitively be an
       | ABI break and it would require careful coordination of all
       | components involved, including shared libraries.
       | 
       | This seems like a perfect application for an rwlock. Threads that
       | throw acquire for reading, so can occur in parallel. Threads that
       | load or unload shared libraries (a pretty rare occurrence outside
       | of process start) acquire for writing and therefore block reads
       | from the table from throwing threads. The shared library loader
       | shouldn't throw during this piece.
       | 
       | Come to think of it, throwing should also be pretty rare. C++
       | exceptions are not really about common control flow, but truly
       | exceptional circumstances. So the high cost serializing parallel
       | throwers doesn't even sound like that huge of a deal. But as I've
       | said, it seems pretty easy to improve upon it.
        
       | _b wrote:
       | I think the dilemma for C++ exceptions is that if the programmer
       | actually thinks something should never happen, it is almost
       | always better to just crash. But if the programmer thinks it
       | might happen sometimes, then it is risky to predict it is will be
       | super rare, as often this code will be called in different
       | contexts in the future, so it is safer to use normal returns and
       | flow control. As a result, throwing an exception is basically
       | never the best thing to do.
        
       | adrian17 wrote:
       | This paper confirmed what I suspected for some time: exceptions
       | are still the only basically-zero-overhead solution available,
       | which ironically completely justifies their existence in usage
       | patterns where exceptions are actually exceptional. There are
       | times when writing/profiling Rust when I wish I had access to
       | exceptions instead of `Result` propagation.
       | 
       | I do agree with the mentioned design issues though.
        
         | dureuill wrote:
         | > There are times when writing/profiling Rust when I wish I had
         | access to exceptions instead of `Result` propagation.
         | 
         | This introduces a whole bunch of design issues, but isn't
         | `panic` (in unwinding mode) basically C++'s exceptions,
         | implementation-wise? Couldn't we use `panic` + `catch_unwind`
         | as a poor man's exception system, should the performance
         | situation really require so?
         | 
         | Also, could we maybe add an attribute `#[exceptional]` to enum
         | variants in a match, that would result in a match
         | implementation closer to exceptions, implementation wise?
        
         | cryptonector wrote:
         | The problem is that if you have exception-happy code, then it
         | becomes a scalability limiter for threading.
        
           | adrian17 wrote:
           | Yes, if A: you have code that's expected to "fail" relatively
           | many times, and B: this code is also multithreaded. A lot of
           | code doesn't match one or both of these conditions while
           | still being perf-sensitive.
           | 
           | Further, as you can see in other comments, it's not an
           | uncommon belief that exceptions simply shouldn't be used for
           | A (and thus it's reasonable that they aren't optimized for
           | something they aren't supposed to be used for).
        
       | zabzonk wrote:
       | > As illustrational example consider this small code fragment:
       | 
       | Consider this crap small code fragment. Any function that returns
       | void is almost certainly wrong.
        
         | turminal wrote:
         | Enlighten us, please
        
           | zabzonk wrote:
           | Any function that does not return a value must have a side
           | effect, else why call it. Calling functions that have side
           | effects (and particularly don't indicate if the side effect
           | was succesful), is not a great idea.
        
             | torstenvl wrote:
             | Are there any software projects of substantial scale that
             | are purely FP?
             | 
             | Even things like I/O or dynamic memory allocation have side
             | effects. So while FP has some great _ideas_ implemented at
             | scale (first class functions and MapReduce perhaps the most
             | well-known), they don 't seem very useful by themselves.
             | 
             | EDIT: I'm not objecting to the idea "functions should
             | return information," I'm objecting to the idea that side
             | effects are "not a great idea."
        
               | zabzonk wrote:
               | You generally want to test if an "I/O or dynamic memory
               | allocation" worked.
               | 
               | BTW, I am basically a C++ programmer, not some functional
               | maniac. But whenever I see a void return type, I think
               | something is wrong.
        
         | burnished wrote:
         | What does 'wrong' mean in this context? I mean, if you're
         | against side effects and thus believe functions should always
         | return a value that seems reasonable. Except that for a toy
         | example where the purpose is to exercise some hardware and
         | measure results it does not seem to be applicable.
        
         | MauranKilom wrote:
         | Good thing printf returns something, otherwise it might wrong
         | or useless!
        
         | worik wrote:
         | No it is not.
        
         | Koshkin wrote:
         | You are right. That's why in good old languages they were
         | called 'procedures.'
        
           | zabzonk wrote:
           | And those languages were badly designed. Which is why we
           | don't have "procedures" or "subroutines" in modern
           | programming languages" - just functions.
        
       | john567 wrote:
       | Why do we need to have ambient control flow? This is what
       | exception handling is, it's a hidden control flow.
       | 
       | I don't use them. I just create an error type and pass that
       | around.
       | 
       | The only legitimate exception I will accept is when you access
       | invalid memory. That's a special case and depending on the
       | environment something extraordinary must happen.
       | 
       | But exceptions and exception handling just creates annoying code.
       | It doesn't add value, not really.
        
         | edflsafoiewq wrote:
         | My problems with Result/expected/etc
         | 
         | 1. They generate syntactic noise at every point they touch the
         | call graph: function signatures, calls, returns.
         | 
         | 2. In particular, if a change causes a deeply nested function
         | that used to always succeed to be able to error, the entire
         | path up the call graph needs to get Resultified.
         | 
         | 3. Since the caller must be aware of them, generic code
         | generally has to be Result-aware too.
         | 
         | 4. They aren't a total solution, exceptions usually have to
         | exist anyway (eg panic), for things like oom/assert/etc. So
         | you're usually paying the cost of them anyway.
         | 
         | 5. You only get what the callee gives you. With exceptions, you
         | can get a backtrace to the cause of the error by default, with
         | no effort needed on the part of the callee.
        
           | exdsq wrote:
           | An FYI that Monads are useful for removing that (left, right)
           | boilerplate from composed functions. This was the issue I had
           | where they finally clicked for me.
        
           | zozbot234 wrote:
           | > They generate syntactic noise at every point they touch the
           | call graph: function signatures, calls, returns.
           | 
           | This is not "noise" but needed information. A function that
           | can error out should not have the same signature as one that
           | will never return an error. Similarly, call-site special
           | syntax (like '?' in Rust) helps address the concerns raised
           | by hidden control flow.
           | 
           | > Since the caller must be aware of them, generic code
           | generally has to be Result-aware too.
           | 
           | True, but the Rust standard library includes a zero type that
           | can be used to mark a Result-aware function as infallible,
           | making it easy to wrap it with a non-Result type.
        
             | gpderetta wrote:
             | I agree that functions should specify in their signature
             | wether and how they fail, but checked exceptions can do
             | that. Additional call site syntax is indeed just noise. I
             | strongly agree with David Abrahams[1] on this. A better
             | solution would be noexcept regions were the compiler would
             | statically guarantee that they can't be left via
             | exceptional control flow.
             | 
             | [1] https://forums.swift.org/t/on-the-proliferation-of-try-
             | and-s...
        
               | zozbot234 wrote:
               | "noexcept" regions would be even _noisier_ than the
               | established pattern of non-fallible calls as the default
               | and some lightweight syntax (such as  '?' in Rust) to
               | indicate fallibility. Sure, if literally _all_ calls were
               | fallible the  '?' or equivalent would be redundant to
               | call syntax, but everyone knows that this is not the
               | case. And it's important that the failure-prone case be
               | acknowledged as such.
        
             | musingsole wrote:
             | > one that will never return an error
             | 
             | Code that is error-safe is so rare. Why adopt a pattern
             | that elevates the normal case ("here be errors") to
             | information you have to disclose at every turn?
        
               | lanstin wrote:
               | Oh my, this sentiment is common. Errors can't happen
               | inside a Turing machine. The errors are just when you
               | step outside the process to interact with externalities.
               | Computations should be thought of as having errors in it
               | them, any more than the integers do. Network or disk
               | calls maybe have all sorts of things happen. You have to
               | think about the failures whenever you have succumbed to
               | reaching outside of your call stack for answers. Because
               | those remote or shared by other processes resources might
               | not reach the same level of certainty, and your perfect
               | code might or might not need the answers or be able to
               | work around their lack.
        
             | stickfigure wrote:
             | > This is not "noise" but needed information.
             | 
             | That is _incredibly_ domain-dependent. In most general
             | business processing, that information is just noise.
             | 
             | Building a web app? 99.9% of the time, you let exceptions
             | get caught by the http server and return 500 to the client.
             | In a few rare cases where you want to do something else,
             | you catch. If you don't catch, the client still gets 500 -
             | a perfectly acceptable fallback.
             | 
             | Building a GUI app? 99.9% of the time, exceptions in the UI
             | loop should just display an error message to the user in a
             | modal dialog and then get ignored. Sure, you can do
             | something else, but the error dialog is a reasonable
             | fallback.
             | 
             | There is no good reason to torture the whole call stack to
             | accommodate these problem domains.
        
             | wvenable wrote:
             | > A function that can error out should not have the same
             | signature as one that will never return an error.
             | 
             | Functions that never fail are pretty rare compared to ones
             | that do. It's easier to just assume that all functions can
             | fail. Mentally it's a much simpler model.
        
               | lanstin wrote:
               | This post is the saddest thing I have ever read in
               | computing. So much power comes about (on the CPU, in the
               | call stack) by being able to assume that things proceed
               | with mathematical tractable properties, as computable
               | functions, plain old general recursion. Distributed
               | things, sure, you have to have protocols and so one to
               | regain the determinism, and even then perfection is no
               | longer achievable, but within your big FSM of
               | CPU/memory/disk, it's all pre-calculated in some platonic
               | ideal of computable, recursively enumerable Platonic
               | world of simplicity and knowability about which we can
               | reason.
        
               | gpderetta wrote:
               | Pragmatically, shit happens.
        
               | wvenable wrote:
               | One errant cosmic particle and all determinism goes out
               | the door as well.
               | 
               | The story of computing is not one of mathematics, it's of
               | people and it's of change. Software is about codifying
               | decisions and without perfect knowledge of the universe
               | those decisions are always going to be wrong in some way.
               | And that's even without introducing the infallibility of
               | programmers. Errors are simply part of the process.
        
           | kccqzy wrote:
           | > 2. In particular, if a change causes a deeply nested
           | function that used to always succeed to be able to error, the
           | entire path up the call graph needs to get Resultified.
           | 
           | This is a pro not a con. It now shows clearly that all these
           | function calls are now failable. Of course at higher levels
           | you may have additional assumptions that you know won't make
           | the low-level functions fail, and you are welcome not to
           | change everything as well.
           | 
           | > 4. They aren't a total solution, exceptions usually have to
           | exist anyway (eg panic), for things like oom/assert/etc. So
           | you're usually paying the cost of them anyway.
           | 
           | Panics do not need to be caught and handled. You can (and
           | should) transform panics into abort.
        
             | mwcampbell wrote:
             | > Panics do not need to be caught and handled. You can (and
             | should) transform panics into abort.
             | 
             | I think this strong stance needs some justification,
             | especially since it's not the default in Rust.
        
               | lanstin wrote:
               | It is in Go Lang which shares error handling philosophy
               | with GP.
        
               | ziml77 wrote:
               | Go panics do not simply abort.
               | 
               | "For a real-world example of panic and recover, see the
               | json package from the Go standard library. It encodes an
               | interface with a set of recursive functions. If an error
               | occurs when traversing the value, panic is called to
               | unwind the stack to the top-level function call, which
               | recovers from the panic and returns an appropriate error
               | value (see the 'error' and 'marshal' methods of the
               | encodeState type in encode.go)."
               | 
               | From https://go.dev/blog/defer-panic-and-recover
        
         | throw10920 wrote:
         | > Why do we need to have ambient control flow?
         | 
         | Because, properly-used, it saves you a lot of effort.
         | 
         | Returning an error type unwinds the stack. If I have a low-
         | level computation that has a number of error cases, and I want
         | the high-level code to intelligently handle some of those error
         | cases and continue processing, that's _not doable_ using return
         | values. Error codes bind the decision of which error-recovery
         | strategy to take (which is only present at a high level) with
         | the details of that strategy (which is only present at a low
         | level).
         | 
         | As a trivial, obviously-fake example - if I have a high-level
         | GUI library that makes use of a low-level division function, I
         | might occasionally divide by zero. Depending on what the GUI
         | library was doing, I might want my divideBy(x, y) function to
         | return 0, 1, the first argument, the second argument, or not
         | return anything because that section of the code will be
         | completely aborted.
         | 
         | Without ambient control flow, if you just have return values,
         | you have to check the return value for every single division
         | operation you perform - and you'll have to re-implement code
         | paths where a division operation failed.
         | 
         | What if you have a long-running operation? If you return an
         | error value when that operation happens, but it turns out the
         | nature of the operation allows you to ignore that error and
         | continue, you'll have to re-start the entire computation. If
         | you hard-code the lower-level logic to ignore errors and
         | continue, then you'll also end up ignoring errors that you
         | really shouldn't.
         | 
         | Error types do not solve these problems - in fact, they require
         | that you duplicate lots of code to, say, make multiple variants
         | of a library that are identical except when it comes to error-
         | handling - or just cause your applications to lose lots of
         | error-handling nuance and bail early on lots of exceptional
         | circumstances that could be recovered from.
        
           | zozbot234 wrote:
           | > What if you have a long-running operation? If you return an
           | error value when that operation happens, but it turns out the
           | nature of the operation allows you to ignore that error and
           | continue, you'll have to re-start the entire computation.
           | 
           | This is just as much of a problem with exceptions. "Fix the
           | error and continue" needs some equivalent to resumable
           | conditions, which are generally implemented at the "low
           | level" using coroutines. My understanding is that async-await
           | as a language feature might be able to express these with
           | relative ease, but exceptions alone clearly do not suffice.
        
         | jdlshore wrote:
         | There are some kinds of errors that can't be handled locally,
         | but do need to be handled globally, or generically higher in
         | the call chain. Continuing execution after the error occurs
         | will make the problem worse. Exceptions allow you to cease
         | execution without putting an if statement after every function
         | call.
        
           | jeffbee wrote:
           | That's what you get from _disabling_ exceptions: a call to
           | std::abort instead of a throw.
        
             | InfiniteRand wrote:
             | In some ways that's throwing an exception into the calling
             | environment in the form of an error code
        
           | WkndTriathlete wrote:
           | Haskell's IO monad says, "Hi! With me you don't need
           | exceptions and you don't need to put an if statement after
           | every function call."
        
             | gpderetta wrote:
             | How is the implicit control flow in do notation different
             | from exceptions?
             | 
             | If anything, the issue is between checked and unchecked
             | exceptions.
        
             | mibsl wrote:
             | Haskell's IO monad actually has excellent exception
             | support, including async exceptions and masking them in
             | critical sections.
             | 
             | There are `Maybe` and `Either` and they're great at
             | streamlining error handling in pure code, but when it comes
             | to IO most libraries just throw exceptions (including the
             | standard library).
        
         | mjw1007 wrote:
         | One of the motivating examples for exceptions, at the time when
         | they were beginning to appear in mainstream languages like Ada,
         | was to allow people to write arithmetic expressions using
         | familiar notation, while still having a place to put an error
         | handler for the overflow case.
         | 
         | Perhaps the lesson of the last forty years or so is that this
         | convenience wasn't worth adding such a heavyweight feature to
         | the language, but it seems to me that modern languages are
         | still weak at handling overflow.
        
           | pjmlp wrote:
           | C++ is the only language where exceptions are such an
           | ideology war.
           | 
           | All the other ones that have born with exceptions don't have
           | this issue, including Ada.
        
             | Koshkin wrote:
             | _There are only two kinds of languages: the ones people
             | complain about and the ones nobody uses._
             | 
             | -- Bjarne Stroustrup
        
             | pyjarrett wrote:
             | Ada exceptions are fundamentally different from C++ since
             | they only carry type data with a possible message and no
             | other user-defined data.
             | 
             | Old school C++ used to have additional try/catch blocks
             | (and performance hit) inserted to ensure that functions
             | match the given exception signature. Also, C++ usually
             | focuses on performance, so all of the bookkeeping required
             | for exceptions could historically be the last 3% or so
             | difference between making your FPS rate or not.
        
               | pjmlp wrote:
               | FPS rate should not drive what 99% of other C++
               | developers are able to use the language for.
        
             | masklinn wrote:
             | I'm not sure that's true.
             | 
             | Exceptions are a big issue in Javascript, though mostly
             | because they're absolutely terrible.
             | 
             | Whether to use exceptions or not is a common question in
             | e.g. C#, and several APIs are duplicated to have both
             | exception-based and values-based variants. And Python is
             | oft criticised for using exceptions more than once every
             | blue moon.
        
               | ncmncm wrote:
               | No legitimate conclusions can be drawn from Java or C#
               | experience, besides that it is best to stay well clear of
               | them.
        
               | pjmlp wrote:
               | It is a big difference to offer both kinds of APIs, or to
               | make endless flamewars with compiler runtime forks, which
               | is what disabling exceptions and RTTI mean in practice, a
               | fork from ISO C++.
        
         | CyberRabbi wrote:
         | > The only legitimate exception I will accept is when you
         | access invalid memory.
         | 
         | If all memory accesses were done with a function call, e.g.
         | read(void *addr), then by your logic there would be no
         | legitimate need for exceptions.
         | 
         | If you extrapolate into the other direction, exceptions are
         | convenient from a syntactic POV because it avoids littering
         | every operation with an explicit error return mechanism.
        
         | wvenable wrote:
         | Exceptions come naturally from the realization that you mostly
         | have to propagate errors to where they can be suitably handled
         | or logged. And all that propagation code heavily detracts from
         | the meaning of the code when you are writing it or reading it.
         | And messing up the propagation is a common source of issues
         | (historically).
         | 
         | With "exceptional" errors are are only 2 real recovery options:
         | restart the operation or terminate the operation. Neither of
         | these are typically decided on anywhere where an error might
         | occur in the call stack.
        
           | jstimpfle wrote:
           | > Exceptions come naturally from the realization that you
           | mostly have to propagate errors to where they can be suitably
           | handled or logged.
           | 
           | I agree of course that errors must be handled and logged as
           | appropriate, but the implication that this has to happen by
           | popping from the call stack is not justified at all.
        
             | wvenable wrote:
             | Logging can be done but I can't see how you could handle
             | errors.
             | 
             | If I'm processing 10 transactions and transaction 8 fails,
             | needs to be retried a couple of times before being skipped
             | for 9, I don't know how you'd do that other than going up
             | the stack to where you're looping over the transaction
             | list.
        
           | ithkuil wrote:
           | Not all errors are "exceptional". For example, consider an
           | API that lets you open a file (local or remote); it may be
           | very common that a file doesn't exist and it may be natural
           | to handle that case by checking a "file not found" error
           | (checking if the file exists before accessing it incurs in an
           | extra cost and it's also racy).
        
           | mort96 wrote:
           | I also have to imagine there's a decent performance boost.
           | With Rust's and Go's (and C's, usually) approach of having
           | 'if (error) { return error; }' all over the place (with
           | syntax sugar or not), there will be a lot of extra branches
           | in the happy path. Sure, those branches are predictable and
           | thus fast, but they're not instant, and they take up icache
           | space in the happy path. Modern exception implementations can
           | almost exclusively slow down the exceptional code, and code
           | which propagates exceptions without catching or throwing will
           | look identical to code with no error handling at all.
           | 
           | I'm sure the gains aren't tremendous, but lots of C++ design
           | decisions are for slight performance improvements at the cost
           | of less safety. Other examples are unchecked array access by
           | default and unchecked overflow. Whether these are the _right_
           | decisions or not is debatable, but at least it 's consistent.
           | 
           | EDIT: Of course, as the article points out, if you have a
           | high error frequency and many cores, exceptions cause
           | significant performance issues. But in the case where
           | exceptions are _actually_ exceptional, and especially in
           | cases where the only real response to an exception is to log
           | an error and exit, exceptions are exceptionally good (pardon
           | the pun) from a performance perspective.
        
         | masklinn wrote:
         | One of the core problems is C++'s design basically requires it:
         | there's no other way to error from a ctor, and since ctors are
         | used as hooks in many operations the dishonest rejoinder of
         | "just use a factory" doesn't work in any capacity.
        
           | mikepurvis wrote:
           | Doesn't some of the reliance on constructors for everything
           | come from how constness is fetishized in C++? Not that it's
           | all bad to have those checks in place, but Python doesn't
           | have this issue with out of control constructors in part
           | because (almost) everything in Python is just
           | unapologetically mutable.
        
             | chippiewill wrote:
             | You could potentially argue that, but certainly Rust
             | doesn't encounter this issue despite being const by default
             | because they simply don't have constructors in the first
             | place.
        
               | mikepurvis wrote:
               | Yeah I knew I was going to get called out with a Rust
               | comparison. I think the Rust approach basically
               | acknowledges the issue with what C++ did-- that automatic
               | initialization is cute but ultimately wasn't worth what
               | it ended up costing in terms of hidden control flow, poor
               | error handling, static initialization issues, etc.
               | 
               | Anyway, Rust basically deals with it by giving the class
               | designer the choice to supply factory functions or punt
               | on it, making the user initialize every field themselves
               | each time. And I think most agree that this is a good
               | approach; it's the best of C++ (factories) with a better
               | fallback than a default constructor.
        
               | gpderetta wrote:
               | But you have exactly the same options in C++.
        
               | mikepurvis wrote:
               | Sure, but the point is that providing factories requires
               | extra work and consideration, so a lot of C++ classes
               | instead lean on the default option of a constructor.
               | Rust's removal of that forces the class designer to
               | choose.
        
               | masklinn wrote:
               | Really? What's the factory function invoked for a copy or
               | a move?
        
               | gpderetta wrote:
               | Whatever you want it to be. By default of the compiler
               | will generate calls to move and copy constructors.
        
               | aphexairlines wrote:
               | It's also easy to provide a default factory:
               | https://doc.rust-lang.org/std/default/trait.Default.html
        
               | mikepurvis wrote:
               | True, but the naming does matter. Calling
               | Thing::default() clearly communicates that your just
               | getting baseline values and not a lot of magical other
               | initialization stuff going on-- with a Thing::Thing() in
               | C++, you're really at the mercy of whatever the project
               | conventions are for how "fat" the constructor is going to
               | be.
               | 
               | I think the naming is also important for cases where
               | there are potentially multiple reasonable defaults, even
               | something as basic as the difference between
               | Vector3::Vector3() and Vector3::zero().
        
           | rr808 wrote:
           | Best to create object then initialize it in a separate
           | method.
        
             | masklinn wrote:
             | Because that meshes so well with copy and move ctors, const
             | and reference members, or inheritance.
        
               | edflsafoiewq wrote:
               | It meshes well with move ctors at least, since objects
               | need a "dead state" they can be put into when moved from.
        
               | Koshkin wrote:
               | This is incorrect. You are not supposed to make any
               | assumptions about the state of an object that has been
               | destructed or moved from. A "dead state" would still be a
               | state, whereas a "dead object" should be thought of as
               | having _no state_ at all, i.e. not being an object any
               | longer.
        
             | ncmncm wrote:
             | By "best" you mean, of course, "worst".
        
               | gpderetta wrote:
               | Amen.
        
       | jmyeet wrote:
       | > 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 wrote:
         | std::expected doesn't require language changes, it's a library
         | type. If anything it shows how C++ is multi-paradigm
        
           | ModernMech wrote:
           | This 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.
        
             | passivate wrote:
             | >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.
             | 
             | But what exactly is wrong with that? I don't quite
             | understand your argument here..
        
             | jerf wrote:
             | I kinda look to when the thing finally stabilizes as a sign
             | as to how bad the problem was. For instance, Javascript
             | front end was a nightmare for a long time, but it seems to
             | have finally stabilized into a reasonable stable
             | configuration with a couple of winners, some minor
             | specialized choices, and the endless churn is now a minor
             | sideshow instead of something that is changing the default
             | choice every six months. There was a bad problem there, but
             | it seems to have been satisfactorily conquered for now. (I
             | expect as static typing creeps ever more deeply into the JS
             | ecosystem that at some point that _may_ cross a critical
             | point and cause some more churn, but at least for now
             | things seem more stable.)
             | 
             | While C++'s churn frequency seems to be higher than the
             | Javascript front end churn frequency, as an outsider, it
             | still seems like "best practices" on C++ are churning
             | around 1.5-2 years, it's been happening for my entire
             | career, and it's _still_ happening. If I seem a bit
             | unsympathetic to the claims that the problems are solved if
             | you just write your C++ code _this_ way now, it 's because
             | I first heard that in 1998 or so, for a set of common
             | practices now considered laughably out of date, of course.
             | 
             | At some point it becomes more cost-effective to just
             | "churn" on to Rust next time, because even though in that
             | same time frame Rust is a younger language that was going
             | through its early design iteration phase it still seems
             | like it has settled into a lower-frequency churn rate
             | lately for "best practices" than C++.
             | 
             | There's probably some interesting and deeply profound
             | reason why C++ just can't seem to stabilize across what is
             | approaching an entire _human generation_ , but I'm nowhere
             | near interested enough in learning it to actually learn the
             | amount of C++ it would take to find it.
        
               | JAlexoid wrote:
               | One of the reasons why I try to avoid C++ is that it's an
               | unopinionated multi paradigm kitchen sink language.
               | 
               | There are great uses and great features, but there are so
               | many of them and everyone has their own opinions.... Even
               | in this thread there's a clear subset of people who
               | "adore" C++ exceptions
        
               | zozbot234 wrote:
               | There's no such thing as an "unopinionated" kitchen sink
               | language. Language features have all sorts of unforeseen
               | interactions that must be handled somehow, and good high-
               | level design is needed to ensure that the interactions
               | are sensible.
        
               | jcelerier wrote:
               | So interesting, to me a language being opinionated is the
               | main reason to put it on the do-not-use bin. I strongly
               | believe that the best way to develop is through embedded
               | domain-specific languages adapted to individual problems,
               | and opinionated languages are always way too limiting
               | regarding that.
        
               | Koshkin wrote:
               | > _C++ just can 't seem to stabilize_
               | 
               | It's not like C++ is oscillating. It continues to
               | improve, which is a good thing.
        
               | JAlexoid wrote:
               | Adding more and more features isn't necessarily an
               | "improvement".
        
               | MauranKilom wrote:
               | Sure, but that in itself is also not an argument. The
               | space of programming languages in general is moving
               | forward and languages keep adding more (usually higher-
               | level) features. C++ is mostly trying to keep up.
        
               | zozbot234 wrote:
               | > it still seems like it has settled into a lower-
               | frequency churn rate lately for "best practices" than
               | C++.
               | 
               | Rust is still adding a ton of new language features,
               | especially around async, compile time code evaluation and
               | the type system (const generics, GAT/HKT, existential
               | types etc.). We'll very likely see further developments
               | in more areas next, e.g. to match C++ developments in
               | parallel and heterogenous compute (GPU's and the like),
               | or to add forms of proof-carrying code, etc.
        
               | dureuill wrote:
               | I'm not convinced they're comparable.
               | 
               | C++20 is adding a module system to C++. That's a major
               | change to the compilation model. It is also adding
               | concepts, a major change in the way templates are to be
               | written. The very article we're commenting on is
               | discussing how exceptions should possibly be replaced by
               | another error report mechanism, several proposals are in
               | flight for this. There's also the "destructive moves"
               | proposals that have the potential to change a lot how we
               | write types.
               | 
               | A telltale sign that these changes are major is that the
               | entire std has to be "modularized" to support modules,
               | and to be modified to support concepts. Similarly if
               | exceptions are to be revised a large chunk of exception
               | using functions from the std would need to be modified.
               | 
               | On the Rust side, I think the only change that has even a
               | comparable impact is const generics (and maybe
               | specialization).
               | 
               | Existential types and GAT will change how to express some
               | traits (allowing a LendingIterator for example), but I
               | don't expect they will affect a large portion of Rust's
               | std.
               | 
               | Also of note is that the Rust changes come to add new
               | systems orthogonal to the existing features (const
               | generics fill an obvious void compared to C++, same with
               | GAT and existential types where Rust's expressivity is
               | limited in comparison with C++ atm). By contrast in C++,
               | the module system comes to replace the headers, and a
               | change to exceptions would replace the current exception
               | system, creating churn.
        
               | jerf wrote:
               | A lot of that is not what I mean by "churn". What I mean
               | by "churn" is _changes in best practice_. Python has been
               | adding a lot of features, but with the possible exception
               | of static typing support, most of them haven 't made many
               | changes to what constitutes _best practices_. They might
               | make  "nicer ways to write that code" but the old styles
               | haven't been deemed _wrong_. async is also not exactly
               | what I mean; this allows new code to be written that
               | mostly couldn 't before. This one is only a partial miss
               | though since it did deprecate some older libraries, but
               | those libraries weren't really deemed "the right answer"
               | either.
               | 
               | C++ is _constantly_ changing what a  "best practice" is.
               | The latest hotness from three-generations-ago churn is
               | now considered broken.
        
             | pjmlp wrote:
             | My first contact with RAII was in 1993 with Turbo C++ 1.0,
             | it is hardly a hot new thing.
        
               | ModernMech wrote:
               | That'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 wrote:
               | For 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 wrote:
               | Destructors have been in C++ since the mid-80s. Anybody
               | who did not know about RAII in 1993 did not know any C++.
        
             | duped wrote:
             | I 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.
        
             | andi999 wrote:
             | How do you get memory without new nowadays?
        
               | kaetemi wrote:
               | make_unique, etc.
        
               | usefulcat wrote:
               | They're probably referring to preferring std::make_unique
               | or std::make_shared to bare new/delete. Using either of
               | the former makes the ownership semantics clear and avoids
               | the need to remember to call delete at the appropriate
               | time.
        
         | fbkr wrote:
         | I'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 wrote:
           | > 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.
        
             | Inityx wrote:
             | It's also worth noting that Rust does also have stack-
             | unwinding error propagation, in the form of
             | `panic`/`catch_unwind`, which can be used as a less-
             | ergonomic optimization in situations like this. Result
             | types like this also don't color the function, since you
             | can just explicitly panic, which would be inlined at the
             | call site and show similar performance to C++ exceptions.
        
             | ncmncm wrote:
             | Using 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 wrote:
               | Using 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.
        
         | jeffbee wrote:
         | You 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.
        
           | kevin_thibedeau wrote:
           | You also have to abandon operator new and STL unless you want
           | to pretend they never fail.
        
             | jeffbee wrote:
             | Right, 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 wrote:
               | It'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.
        
           | jmyeet wrote:
           | Except if that's incompatible with libraries you're using.
           | 
           | For the record, I like absl::StatusOr<>.
        
         | layer8 wrote:
         | Regarding 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 wrote:
           | Java'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-
           | exc...
        
             | hashmash wrote:
             | Most of the software I write is designed to be fault-
             | tolerant, and checked exceptions are fantastic way of
             | detecting potential faults. The problem is that checking is
             | baked into the exception definition instead of its usage.
             | 
             | If I declare "throws NullPointerException", then this
             | should mean I want it to be a checked exception. This
             | should force the caller to catch the exception, or declare
             | throwing it, or to simply throw it out again without
             | declaring it. This would effectively convert the checked
             | exception into an unchecked exception.
             | 
             | Converting a checked exception to an unchecked exception is
             | possible in Java, and I do it whenever it makes the most
             | sense instead of wrapping the exception, which is just a
             | mess. Unfortunately, there's no way to make an unchecked
             | exception behave as if it was a checked exception. It's
             | easier to reduce fault tolerant checks then to add more in.
             | 
             | Some might argue that converting an exception to be
             | unchecked is a bad idea, but this sort of thing is done all
             | the time in JVM languages that are designed to interoperate
             | with Java. If I call a Scala library from Java, and that
             | library is doing I/O, then IOException has now effectively
             | become unchecked.
        
               | layer8 wrote:
               | The distinction some Java programmers make (including
               | myself) is to treat RuntimeExceptions as indicating
               | interface contract violations (usually preconditions), or
               | more generally, bugs. That is, whenever a
               | RuntimeException occurs, it means that either the caller
               | or the callee has a bug. When existing APIs use
               | RuntimeExceptions to indicate any other error condition,
               | they are wrapped/converted into a checked exception ASAP.
               | 
               | I understand your point about usage-dependend checking.
               | However, I believe it is mistaken. Consider a call chain
               | A -> B -> C -> D, where D throws a checked exception of
               | type X, which C converts into an unchecked exception
               | (still type X). At the same time, B also calls other
               | methods that happen to throw a checked X, and thus B
               | throws a checked X itself, which now, unbeknownst to B,
               | also includes the X from D. B documents the semantic of
               | its X exception, which may not fit the ones thrown by D.
               | Now A catches X, believing the semantics as documented by
               | B, but actually also catches X from D, which was
               | concealed (made unchecked) by C. This breaks checked
               | exceptions in their role as part of an interface
               | contract.
               | 
               | The fact that unchecked exception may originate from
               | arbitrarily deep in the call chain is also the reason why
               | they are unsuitable for defining interface contracts, A
               | function declaring certain semantics for a particular
               | unchecked exception can't realistically ensure those
               | semantics if any function it calls itself must be assumed
               | to also throw exceptions of that type (because it is
               | unchecked). Effectively, you can't safely document
               | "exception X means condition Y" for your function if any
               | nested calls may also throw X for unknown reasons.
        
             | mcguire wrote:
             | " _Java 's checked exceptions are generally regarded as a
             | mistake._"
             | 
             | By people who do not want to believe that errors are part
             | of a system's API and prefer to just write the happy path
             | and let any exception kill the process. And who don't mind
             | getting called at 2:00 am because a dependency buried deep
             | in a subsystem threw an exception that you'd never heard of
             | before.
        
               | lanstin wrote:
               | The compiler makes them part of the API. And what a lot
               | of people do is just throw in a bunch of blanket catches
               | with empty code. Although some of this is server vs.
               | desktop software - the article was about complex GUI
               | apps, not long running servers. Tho I personally think
               | long-running servers shouldn't use exceptions. Each call
               | to something out of the running code stack frame should
               | explicitly decide what to do on failure or "didn't hear
               | back." That's how your server gets to be bullet proof
               | (and by bullet proof, I don't mean "auto-restarts on
               | unhandled exception.")
        
               | nybble41 wrote:
               | > By people who do not want to believe that errors are
               | part of a system's API...
               | 
               | The point was that you should be _returning_ errors, not
               | throwing them. Runtime exceptions (null reference,
               | division by zero, out of memory, etc.) ought to indicate
               | a fatal error in the (sub)program or runtime environment.
               | You can trap these, and report them, but it 's usually a
               | mistake to try to case-match on them. Unlike errors,
               | which are predictable, enumerable elements of the design,
               | runtime exceptions should be treated as an open set.
        
               | taeric wrote:
               | I disagree with this. But, I'm also a fan of the
               | condition system in Common Lisp.
               | 
               | That is, if the problem is likely one that needs
               | operator/user intervention, the non local semantics of
               | exceptions makes a ton of sense. Indeed, it is useful to
               | have a central handler of "things went wrong" in ways
               | that is cumbersome if every place is responsible for
               | that.
        
               | nybble41 wrote:
               | If you read the article by Anders Hejlsberg, he's not
               | arguing against centralized handling of exceptions--the
               | handling of runtime exceptions is expected to be
               | centralized near the main program loop. That, however, is
               | a general-purpose handler which won't have much logic
               | related to any particular _kind_ of exception; it just
               | reports what happened and moves on. You don 't need
               | checked exceptions for that.
               | 
               | The condition system in Common Lisp (which I am also a
               | fan of BTW) is designed around dealing with conditions
               | _when they occur_ , whereas most of the alternatives
               | focus on the aftermath. In particular, conditions don't
               | unwind the stack before running their handlers, which
               | makes it possible to correct the issue and continue,
               | though handlers can naturally choose to perform non-local
               | returns instead. More to the point, there is no
               | requirement to annotate Common Lisp functions with the
               | conditions they may raise, which makes them more akin to
               | _unchecked_ exceptions.
        
               | taeric wrote:
               | Fair. Sounds like you are more claiming that most
               | functions would be better returning a result type, but
               | some will be better with more?
               | 
               | I view this as I want my engine to mostly just work. It
               | may need to indicate "check engine" sometimes, though.
               | And that, by necessity, has to be a side channel?
               | 
               | I think that is my ultimate dream. I want functions to
               | have a side channel to the user/operator that is not
               | necessarily in the main flow path. At large, I lean on
               | metrics for this. But sometimes there are options. How do
               | you put those options in, without being a burden for the
               | main case where they are not relevant?
        
               | zmmmmm wrote:
               | > errors are part of a system's API
               | 
               | This is a really interesting and nuanced point here. The
               | article linked above [1] talks a bit about it. The
               | problem is, they both are and aren't part of the API in
               | the strict sense.
               | 
               | In the sense that they specify a contractual behaviour
               | they are part of the API of a function. But in the sense
               | that they are something the caller should / needs to
               | specifically care about, they sit in between. That is, in
               | the vast majority of cases, the caller does not care
               | specifically _what_ exception occurred. Generally they
               | want to clean up resources and pass the error up the
               | chain. It is  "exceptional" that a caller will react in a
               | specific way to to a specific type of exception. So this
               | is where Java goes wrong because it forces the fine
               | grained exception handling into the client when the
               | majority case (and preferred case generally) is the
               | opposite. It makes you treat the minority case as the
               | main case. There are ways to work around / deal with this
               | but nearly all of them are bad. The article talks about
               | some of the badness.
               | 
               | I do think it's interesting though that Rust has taken
               | off and is generally admired for a very similar type of
               | feature (compiler enforced memory safety). I am really
               | curious how that will age, but so far it seems like it is
               | holding up.
               | 
               | [1] https://www.artima.com/articles/the-trouble-with-
               | checked-exc...
        
             | taeric wrote:
             | I think the question is more on if the implementation of
             | how the jvm does exceptions somehow less affected by core
             | count?
             | 
             | That is, the checked part is just a language
             | implementation, right? The jvm doesn't really make much of
             | a distinction. (This is meant as a check to my assumption.)
        
               | layer8 wrote:
               | Yes, that's right, as static type checks generally are.
               | However, the C++ performance issues are unrelated to
               | whether exceptions are statically and/or runtime checked.
               | Furthermore, Java exceptions are not particularly
               | efficient, in particular because they collect the current
               | stack trace on creation by default, which is a relatively
               | expensive operation.
        
               | taeric wrote:
               | I thought you could tune away the trace on creation
               | behavior.
               | 
               | Regardless, I'd be interested in seeing if this is a
               | performance bottleneck. I'd guess it is only relevant on
               | dataset processing. Closer you are to a place that
               | legitimately can toss to a user, more likely you are to
               | not care?
               | 
               | That is, if the common case of an exception is to stop
               | and ask for intervention, is this a concern at all?
        
               | layer8 wrote:
               | > I thought you could tune away the trace on creation
               | behavior.
               | 
               | You can when you implement your own exception type, but
               | not in general (and doing so would break too many
               | things).
               | 
               | Exceptions are thrown and caught quite frequently in Java
               | for "expected" cases, for example when attempting to
               | parse a number from a string which is not a valid number.
               | It's generally not a performance problem, and stack trace
               | collection is probably heavily optimized in the JVM.
               | Nevertheless, it's certainly still a lot slower than C++
               | single-threaded exceptions. You have to realize that even
               | a factor of 100 slower may be unnoticeable for many use
               | cases, because so much else is going on in the
               | application.
        
         | jkaptur wrote:
         | I'm relatively new to C++ and I found absl::StatusOr<T> and its
         | helper macros to be extraordinarily pleasant.
        
           | alskdjflaskjdhf wrote:
           | Yep, 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).
        
           | kirbyfan64sos wrote:
           | It has helper macros now? I liked StatusOr but had to write
           | my own helper macros for it at the time.
        
             | trimbo wrote:
             | Ex: https://google.github.io/or-
             | tools/cpp/status__macros_8h_sour...
        
         | [deleted]
        
       | vlovich123 wrote:
       | Wouldn't changing the global mutex into a read/write be a simple
       | way to fix things? Shared libraries changing the exception table
       | at the same time as exceptions being thrown seems rare. Might
       | also be fixable in an API-preserving way...
       | 
       | Edit: nope. This idea is discussed later in the paper (not fully
       | ruled out but the answer may still require ABI changes for more
       | subtle reasons)
        
         | interroboink wrote:
         | I think this is addressed in the article in the section
         | starting "A less radical change would be to change the global
         | mutex into an rwlock, but unfortunately that is not easily
         | possible either..."
        
         | plorkyeran wrote:
         | > A less radical change would be to change the global mutex
         | into an rwlock, but unfortunately that is not easily possible
         | either. Unwinding is not a pure library function but a back and
         | forth between the unwinder and application/compiler code, and
         | existing code relies upon the fact that it is protected by a
         | global lock. In libgcc the callback from dl_iterate_phdr
         | manipulates shared state, and switching to an rwlock leads to
         | data races. Of course it would make sense to change that, but
         | that would be an ABI break, too.
        
         | Findecanor wrote:
         | Could the mutex be wrapped in a rwlock? New code that don't
         | share state would be able to unwind concurrently while old code
         | would be locked out.
        
       | The_rationalist wrote:
        
       | ziml77 wrote:
       | I find this analysis strange. Yes, C++ does need ergonomic ways
       | to return an error without dynamic allocation (Rust's magic of ?
       | combined with the From/Into traits is nice), but I don't know why
       | you'd analyze the performance impact when you have many failures.
       | If a failure is that common, then you aren't supposed to be using
       | exceptions. It's not really an exceptional circumstance at that
       | point.
        
         | tneumann wrote:
         | That argument is only true in single threaded applications, at
         | least given today's exception implementations. The more threads
         | you have, the more problematic exceptions become. On the large
         | machine you start to see performance problems with 0.1% failure
         | rate, which is not that much. An core counts continue to rise.
        
           | PaulDavisThe1st wrote:
           | Again, most experienced C++ programmers would say that even a
           | 0.1% "normal" failure rate indicates a situation that should
           | not be handled by exceptions. You seem to be describing a
           | style of programming where there's a bunch of work to be
           | done, most efforts to do the work will succeed, a few will
           | predictably (albeit randomly, perhaps) fail. While I would
           | concede that you might conclude that exceptions are the
           | perfect tool to use to deal with the failures...
           | 
           | 1) you're describing a fairly unusual application behavior 2)
           | it's only because of the performance goals that the speed of
           | exceptions matters
           | 
           | I'm the lead dev of a cross-platform DAW where our "inner
           | loop" is real-time constrained by hardware. We use exceptions
           | freely (in a multicore, multithreaded context) with no
           | performance issues, _because if they ever happen, things must
           | stop_.
        
             | lanstin wrote:
             | True but also libraries and people thinking differently.
             | Numeric is not real-time, tho it has many common
             | characteristics.
        
             | olvy0 wrote:
             | I do the same.
             | 
             | I reserve exceptions for a really exceptional situations.
             | 
             | My motto is that if an exception happens, the program
             | should crash since it's an unrecoverable error. That's just
             | my opinion by I try to enforce that in my own codebase.
             | 
             | My main beef with exceptions is that sneak past the type
             | system. Unlike Java with its exception specification, in
             | C++ we have no idea what to catch. Yes, there's
             | documentation, but it's often hard to keep it in sync with
             | the code. I've been bitten many times in the last couple of
             | years by surprising exceptions jumping from deep inside
             | innocent function calls, either because of a deep
             | dependency or because someone just threw one and didn't
             | document it (that someone was also a younger me on
             | occasion...)
             | 
             | So my design criteria for exception is, is this error here
             | unrecoverable for the application?
             | 
             | The 2 examples in OP's paper are 2 functions, and we have
             | no context. If they were a part, say, of a console
             | application that was used in a nightly cron job, and there
             | was no user to ask for proper input, then yes, maybe
             | crashing the application is acceptable. I would still have
             | liked to print something to a log.
             | 
             | If this was a part of an interactive program, then a proper
             | error code / std::optional / other error object about the
             | illegal value and its place in the array should be
             | returned, with a proper error message displayed to the user
             | about logged. This I'd do by having a top-level catch
             | handler. A single one for all the application.
        
         | masklinn wrote:
         | > I don't know why you'd analyze the performance impact when
         | you have many failures. If a failure is that common, then you
         | aren't supposed to be using exceptions.
         | 
         | The problem is in that case you get into a probabilistic
         | estimation, which is a nice way to say you roll the dice on
         | your performances: what's the ratio at which exceptions are too
         | costly or sufficiently cheap? And how does that impact your
         | level of service if e.g. exceptions are extremely rare but they
         | tend to all affect the same request or workload?
        
           | HelloNurse wrote:
           | If the program fails too often or too unfairly, why should
           | you blame how such failures are handled? The only reasonable
           | "probabilistic estimation" is that something that happens 10%
           | of the time is normal and it shouldn't be treated as an
           | "exception", even if actually thrown exceptions were fast.
        
             | masklinn wrote:
             | > If the program fails too often or too unfairly, why
             | should you blame how such failures are handled?
             | 
             | Because the discussion is about methods of reporting and
             | handling failure?
             | 
             | > The only reasonable "probabilistic estimation" is that
             | something that happens 10% of the time is normal and it
             | shouldn't be treated as an "exception"
             | 
             | That makes no sense whatsoever, and doesn't address the
             | question.
             | 
             | And even if a ctor fails at a rate of 0.9, it has to report
             | errors via exceptions, because that's the only mechanism
             | available to ctors.
        
               | im3w1l wrote:
               | You could use an output pointer parameter. You could
               | construct an object which self-reports as invalid. You
               | could take an error handler callback. You could use a
               | global variable.
               | 
               | Or I think my preference would be a private constructor,
               | and a friend-function that returns value-or-error.
        
           | xrikcus wrote:
           | Adding to that once exceptions start being thrown, that ratio
           | changes because the cost is so high. It's not hard for a
           | service to reach a failure rate just high enough that it
           | overwhelms the system.
        
         | JAlexoid wrote:
         | Errors and exceptions aren't necessarily low in all cases.
         | 
         | And in any case this is about how these exceptions impact
         | parallel processing - and the impact is not negligible.
        
         | CJefferson wrote:
         | Why am I "not supposed to use exceptions"? I don't understand
         | why exception should be used when errors occur occasionally, or
         | when they occur 50,000/sec. They are a control flow method for
         | when errors occur.
        
           | MauranKilom wrote:
           | For the same reason you may want to use or not use memory
           | allocations when doing something 1 time per second or 1
           | million times per second. Performance is not amenable to
           | arguments about degree (especially when talking about this
           | many orders of magnitude in degree).
        
             | gpderetta wrote:
             | But again, the question is whether exceptions need to be
             | slow.
             | 
             | If I'm writing a string_to_int function today I would
             | return an optional<int> (or some Either variant). But if
             | exceptions were cheap I would use them. If I'm catching an
             | exception in the context of immediate caller and the
             | compiler inlines the calle, it ought to be able to optimise
             | the exceptional path away. But it doesn't happen.
        
       | GnarfGnarf wrote:
       | I manage 1.2MLOC C++ developed over 25 years. I am pleased to say
       | we never had to use exceptions. Just check the return value.
        
         | aeldidi wrote:
         | How do you guys handle using other libraries? Everything I can
         | see uses stuff like std::vector and the like at API boundaries,
         | and I'm not aware of a way to construct an std::vector without
         | throwing an exception.
        
       | tsimionescu wrote:
       | Very interesting that exceptions have much less overhead on the
       | happy path than Rust/Haskell style Either types (std::expected):
       | 
       | > For fib we see a slowdown of approx. 60% compared to
       | traditional exceptions, which is still problematic.
        
         | masklinn wrote:
         | > Very interesting that exceptions have much less overhead on
         | the happy path than Rust/Haskell style Either types
         | 
         | C++ genuinely traded "error" cases (made them even costlier) so
         | that the happy path of exceptions would be cheaper. I don't
         | remember whether that's the case in C++, but in some
         | language/implementations (used to be a big issue in V8) just
         | having a try/except would drastically deoptimise a function,
         | even if the exception never happened.
        
         | fpoling wrote:
         | I like how Zig implements exceptions. It is similar to the
         | restricted exception value type proposal for C++, but it also
         | has a nice bonus of recording some stack trace on the unwind
         | path. Plus in Zig any call to a function has to either catch
         | the exception or annotate the call somewhat similar to ? in
         | Rust.
        
         | brundolf wrote:
         | I'm curious whether Rust's Result gets special optimization
         | treatment from the compiler that std::expected doesn't
        
         | yakkityyak wrote:
         | I'm not convinced a recursive implementation of `fib` is a
         | reasonable example to draw such a conclusion.
        
           | fwsgonzo wrote:
           | It really isn't.
           | 
           | People need to stop making trivial benchmarks and draw ...
           | silly ... conclusions.
           | 
           | http://www.eecs.northwestern.edu/~robby/courses/322-2013-spr.
           | ..
           | 
           | It is a fact that C++ exceptions are largely low overhead,
           | and that you also don't have to use them. In fact, C++ can
           | make everyone happy, because you can choose which parts you
           | like.
           | 
           | Personally, knowing how exceptions are implemented and having
           | implemented small parts of the ABI, I can safely say that I
           | will be using exceptions where appropriate in all my C++
           | projects. Parts where real-time behavior is needed we can use
           | custom containers that don't randomly exit on failure. On
           | low-memory hardware it is beneficial to have either tiny
           | exception footprint or no exceptions at all.
           | 
           | It is true that C++ exceptions as they are implemented can be
           | improved upon, breaking ABI. There are also other contenders
           | (Herbceptions) that show great promise, bringing another way
           | of handling exceptions that is not source compatible. Either
           | way, many who work with custom C++ code do not care much
           | about ABI, as everything is source compiled, and such would
           | benefit from a new ABI generation.
           | 
           | Zero-overhead exceptions by Herb Sutter: http://www.open-
           | std.org/jtc1/sc22/wg21/docs/papers/2018/p070...
        
             | tneumann wrote:
             | Of course the fib example is extreme. But some code bases
             | do a lot of calls, and people care about calling overhead.
             | I think a moderate calling overhead like, e.g., with
             | Herbecptions is acceptable, because usually people doing
             | something useful in a function that will mask the calling
             | overhead. But other approaches are really to expensive to
             | justify, they violate the zero-overhead promise of C++.
        
             | fpoling wrote:
             | It is not trivial in practice to make code safe in presence
             | of exceptions even if one follows the best C++ practices.
             | The errors can be very subtle and hard to identify. It is
             | another reason besides the performance and code bloat why
             | Chromium disables them.
        
       | [deleted]
        
       | forrestthewoods wrote:
       | > Nevertheless the overhead is so high that std::expected is not
       | a good general purpose replacement for traditional exceptions.
       | 
       | This is basically the path Rust has chosen. I'm curious if it's
       | _actually_ too slow for C++. I feel like the answer must be no.
        
         | ncmncm wrote:
         | It is always easy to find places where performance doesn't
         | matter, and even Python is fast enough, including startup code
         | in programs where performance otherwise does matter.
         | 
         | You cannot draw useful inferences from those cases.
        
         | synergy20 wrote:
         | same as golang, I doubt that causes serious performance issues
         | but have no proof.
        
       | synergy20 wrote:
       | C++ STL is a strong selling point for c++, disabling exceptions
       | mean you lose all of STL in the library, unless you're fine to
       | use STL without any error reporting at all.
       | 
       | In the gaming case(no rtti, no smart pointer, no exceptions(thus
       | meaning ctor|dtor are "unsafe")), what else do you leave with c++
       | then?
       | 
       | I use c++ but I'm always struggling with yes-or-no for
       | exceptions.
       | 
       | c++ is deeply rooted with exceptions, bad or good.
        
         | Rarebox wrote:
         | There's very little you need exceptions for with STL. Data
         | structures, algorithms, etc. all work just fine without
         | exceptions.
         | 
         | C++ without exceptions is great. Google doesn't use exceptions
         | and they still use STL. Gamedevs usually don't use exceptions
         | and they're fine. Just use some other mechanism for errors,
         | like error codes, absl::Status, std::expected, etc.
        
           | kaetemi wrote:
           | Gamedevs usually don't handle any errors at all.
        
           | synergy20 wrote:
           | Google did not do exceptions due to legacy code base reason,
           | in its announcement it says for new code it will do
           | exception, it just had too many old code and can't do
           | exceptions.
           | 
           | Most STL operations and iteration etc will throw exception
           | for errors, if you disable exception, any of those errors, be
           | it recoverable or not, will just std::terminate, which might
           | not be ideal.
        
           | fpoling wrote:
           | Note Google explicitly configure runtime to instantly crash
           | on memory allocation errors. With that STL works indeed
           | nicely without exceptions.
        
             | lanstin wrote:
             | I interviewed at Google once and during some whiteboard
             | forgot to check malloc for NULL return, then mentioned, "oh
             | yeah at <former company> malloc never returns NULL" and the
             | interviewer commented "at Google, malloc spawns a new data
             | center."
        
       | cryptonector wrote:
       | (Exceptions are bad, full stop. It should all have been monadic
       | all along, with Maybe/Either/Result.)
       | 
       | That said, exceptions should have been allocated on the stack,
       | and catch handlers should have been closures that get called with
       | the exception object. I guess this can't be implemented now.
       | 
       | As for multi-threading unwinding, the issue there is that
       | unwinding tables are part of the shared objects whence the
       | associated object code comes, and it often has to be possible to
       | unload loaded shared objects, so now what? The situation
       | shouldn't be bleak though: a shared object should never be
       | unloaded while there are threads executing its code, so it should
       | be possible to arrange a slow unload-time operation to update the
       | the process-wide unwinding tables -- think of user-land RCU if
       | you like.
       | 
       | The exception handling code should not have to synchronize around
       | unwinding, except that when unwinding completes it might have to
       | notice that there's a pending unload, so wake the thread that is
       | waiting for unwinding. Or maybe not even, because maybe unloading
       | could do something truly breathtaking like check that no thread's
       | stack includes return addresses from the object to be unloaded,
       | and then unwinders would never need to step into the unwinding
       | tables from that object.
        
       | shiado wrote:
       | This somehow reminded me, wasn't there a competition years back
       | to see who could generate the most compiler error output with the
       | least amount of C++? A few too many templates and you could
       | generate terabytes. Edit: don't think it was this but this is
       | still a fun read
       | https://codegolf.stackexchange.com/questions/1956/generate-t...
        
       | CyberRabbi wrote:
       | I am glad more people are raising the issue with C++ exceptions.
       | Unfortunately I don't think the argument made in this article is
       | compelling enough. Bjarne is against replacing the current model
       | and has written an entire article responding to various
       | criticisms of the current model. http://www.open-
       | std.org/jtc1/sc22/wg21/docs/papers/2019/p194...
       | 
       | In particular he has already responded to the efficiency argument
       | with the counter argument that current implementations can be
       | optimized. Even if that optimization process breaks ABI
       | compatibility, it's still better than breaking source
       | compatibility, which is usually what is being proposed. He is
       | right about that so I don't think efficiency arguments are going
       | to sway him.
       | 
       | I used to embrace C++ exceptions until I had to use C++ in non-
       | conventional environments. For me C++ exceptions are wholly
       | inappropriate for real-time programming since it's difficult to
       | statically quantify how much time an exception handling sequence
       | may take. There's also the issue of it requiring malloc() which
       | has its own issues from an interface standpoint in the real-time
       | context. To avoid unbounded malloc, you'd have to set aside a
       | per-thread area for exception storage and require that you never
       | throw an exception value past a certain size.
        
         | chippiewill wrote:
         | Probably worth mentioning that while Bjarne is the creator of
         | C++ and his opinion holds a lot of weight, he's also one of the
         | few language creators who doesn't hold a BDFL hat (and hasn't
         | held that kind of control for a very long time).
        
         | fpoling wrote:
         | If the current model stays in place, then there would be no
         | possibility to reconcile the de-facto fork of C++ into code
         | disabling exceptions and code using it with the former can be
         | running on more CPUs than the latter. In turn that leads to
         | monstrosity like file system access API that try to have
         | exception and exception-less versions of each function.
        
       | grandinj wrote:
       | Counterpoint: I do a lot of perf work on LibreOffice, which makes
       | extensive use of exceptions, and I have never ever even seen the
       | exception throwing show up on a profile, let alone become a
       | problem.
       | 
       | I think this paper started with a conclusion, and worked
       | backwards to justify it.
        
         | brundolf wrote:
         | It probably depends a whole lot on whether exceptions are used
         | only for exceptions, or for control-flow. The author seemed to
         | be treating this question fairly pessimistically
        
         | passivate wrote:
         | Do you still have any notes from your profiling?
        
         | nicoburns wrote:
         | I feel like the main problem with exceptions isn't the
         | performance, it's the non-local control flow and the fact that
         | it makes enumerating all possible failure modes almost
         | impossible. IMO the only way that exceptions could be justified
         | (other than being the status quo) is if they were much _faster_
         | than Result /Maybe types. Performance parity doesn't make them
         | worthwhile!
        
         | CJefferson wrote:
         | Counter-conterpoint: I've worked on two applications C++ which
         | we had to "de-exception", as exception handling was taking >25%
         | of the time when we profiled.
         | 
         | You could argue we were using "too many exceptions", but to me
         | it's the obvious way to unwind in C++.. except it's not fast
         | enough, so we had to switch to a proto-Rust (this was before
         | Rust) style system.
        
         | anarazel wrote:
         | When I read the title, I was assuming it was going to be about
         | worsened code generation when building with exception support.
         | Not about, basically, throwing exceptions as fast as possible.
         | That seems an odd concern given the current performance.
        
         | mlinksva wrote:
         | Thanks a lot for your perf work on LibreOffice, which
         | https://gerrit.libreoffice.org/q/Grandin seems to confirm!
        
         | addaon wrote:
         | I wouldn't necessarily expect to see throwing show up on a
         | statistical profile. I would expect to see (and very much have
         | seen) throwing showing up as a huge latency spike, leading to
         | user-visible non-responsiveness. Especially in a cold situation
         | (first throw after launch, or throwing after pages have been
         | ejected), a surprising number of memory pages need to be
         | populated to throw an exception.
        
         | downut wrote:
         | In 30 years of c++ I've never seen error checking + throw in a
         | tight numeric intensive loop like this. Sure, somebody could do
         | it. I'd have a chat with a coworker who wrote something like
         | that.
         | 
         | I only use exceptions for cleanly unwinding the stack when an
         | unrecoverable error occurs. I design and implement my code so
         | that is _rare_.
        
           | olvy0 wrote:
           | Totally agree, I do the same, up to and including having a
           | chat with a coworker who wrote code like that. I just did
           | that last week, in fact.
           | 
           | Code that uses exceptions like this is a code smell,
           | especially when performance is important. Using exceptions as
           | control flow instead of if/loops is not a good design, IMO.
           | 
           | Note that this is in C++. I consider this style of writing
           | not idiomatic, despite the STL doing it. I use either error
           | return codes or std::optional (itself having a set of
           | problems but IMO better than exceptions).
           | 
           | I'm more willing to accept this kind of code in Java, where
           | it's more or less idiomatic. Less so in C#, where the
           | tendency in the last 15 years has been to use the Try__
           | method instead of throwing exceptions.
        
         | henrydark wrote:
         | As per the piece, open 128 documents on a 128 core machine, and
         | you'll see the difference.
        
           | netr0ute wrote:
           | How many people actually use a 128 core machine though? The
           | absolute biggest AMD Threadripper you can get only has 64.
        
             | mcguire wrote:
             | No one needs more than 64 cores?
        
             | nybble41 wrote:
             | > The absolute biggest AMD Threadripper you can get only
             | has 64.
             | 
             | You can get a machine with dual 64-core AMD EPYC 7662
             | CPUs[0] for a total of 128 cores. It will cost you almost
             | $20k, though--for the most basic configuration.
             | 
             | [0] https://bizon-
             | tech.com/bizon-x6000.html#959:8714;960:4858;96...
        
             | jrockway wrote:
             | Those Threadrippers have two vCPUs per core, so you can
             | definitely have 128 things that appear to proceed in
             | parallel.
        
             | tneumann wrote:
             | A dual socket EPYC costs about 15K. You can rent some
             | online for 400$ a month. This gives you 128 cores for a
             | relatively low price.
        
             | henrydark wrote:
             | 96 virtual cores is a popular choice on AWS, being cost
             | effective in some industry scenarios. I'm certain the
             | analysis of the piece applies
        
         | mhh__ wrote:
         | This is mostly my opinion too.
         | 
         | My argument against exceptions is basically everything other
         | than performance. To make a performance decision you have to
         | build the code incrementally with feedback from how it is
         | actually going to be used e.g. if almost every parse fails then
         | you probably don't want to throw whereas if your code almost
         | always succeeds you probably want your register back (i.e. use
         | exceptions)
        
         | tneumann wrote:
         | I am the the original author, and trust me, I am describing a
         | real world problem. I run massive parallel data processing
         | tasks on machines with 128 cores, and unfortunately some of
         | them produce errors deep within the processing pipeline. From a
         | programming perspective exceptions would be ideal for that
         | scenario, but they cause severe performance problems.
         | 
         | Just think about this: If you have a 100 cores, and 1% of your
         | tasks fail, one core is constantly unwinding. And due to the
         | global lock you quickly get a queue of single threaded
         | unwinding tasks. And things become worse, we expect to have
         | machine with 256 cores soon, and there it is even more
         | dangerous to throw.
         | 
         | If you do not believe me look here: http://wg21.link/p0709 It
         | lists quite a few applications that explicitly forbid
         | exceptions due to performance concerns.
        
           | jcelerier wrote:
           | > I am the the original author, and trust me, I am describing
           | a real world problem.
           | 
           | for a more-or-less niche part of "real world". The
           | overwhelming majority of desktop GUI apps rely on some C++
           | system - Qt, gtkmm, Wx, Blink, Gecko, FLTK, etc etc... and it
           | is not an issue for those, for what exceptions are commonly
           | used for (a write failing because the user disconnected the
           | USB drive while it was copying, a system resource limit
           | exhausted.. things like that). As much as massive parallel
           | data processing tasks matter, I'd really prefer my language
           | to not side-step writing end-user apps for something that
           | happens at $bigcompany or $bigresearchlab.
           | 
           | here's the list of binaries that link against libstdc++.so.6
           | in my /usr/bin:
           | https://paste.ofcode.org/gfZJwx4puVx7Uxy9a7BBU3 - don't
           | forget those please :)
        
             | mcguire wrote:
             | Are you arguing that C++ is unsuitable for massive parallel
             | data processing tasks?
        
               | jcelerier wrote:
               | No, of course not. But let's not change the language in
               | ways that benefits those use cases at the detriment of
               | more common use cases.
               | 
               | Of course, if there are ways to keep more or less the
               | same semantics, while increasing performance, by all
               | means it should be done !
        
               | garbagecoder wrote:
               | No, he's arguing that he'd prefer it not be if that means
               | breaking support for end user apps which is what the OP
               | is arguing for.
        
               | PaulDavisThe1st wrote:
               | for ones that throw an exception for 1% of a unit
               | computation, perhaps yes.
               | 
               | But as has been noted, that's a lot of failures and
               | exceptions may not be the appropriate mechanism to deal
               | with this type of failure.
        
               | jandrese wrote:
               | I think he's arguing that C++ exceptions are unsuitable
               | for parallel data processing, at least in cases where
               | exceptions are regularly thrown.
        
               | ncmncm wrote:
               | There is the red flag: "regularly thrown". "Regularly" is
               | opposite to "exceptionally".
        
               | jcelerier wrote:
               | If exceptions are regularly thrown the software has a bad
               | design and must be fixed. Non-exceptional stuff must of
               | course not be handled through exceptions - no exception
               | should ever be thrown if the software operates as it is
               | expected to.
        
               | gpderetta wrote:
               | Well of course, they are slow so they are unsuitable. The
               | issue is whether they need to be slow.
        
           | [deleted]
        
           | tjungblut wrote:
           | If you run into contention issues have you tried scaling
           | horizontally? Running on 128 single core VMs should mitigate
           | what you see.
        
             | nyanpasu64 wrote:
             | If you want to avoid a global process lock, multiple
             | processes will have less orchestration and communication
             | overhead than multiple VMs (though still a lot more than
             | necessary with threads within a process).
        
             | CyberDildonics wrote:
             | That would turn communicating between threads into
             | communicating between VMs, which is a disastrous change in
             | itself.
        
               | gpderetta wrote:
               | It would. On the other hand multiprocessing with
               | explicitly shared memory could be a fast (but potentially
               | painful) workaround.
        
               | CyberDildonics wrote:
               | That could make sense for processes, which would also get
               | around the exception unwind lock, but I don't know how
               | that would work with every thread moved into a separate
               | VM.
        
               | gpderetta wrote:
               | Of course. Using VMs here would very deep into
               | overengineering territory.
        
               | tjungblut wrote:
               | how do you know the threads need to communicate with each
               | other?
        
           | ncmncm wrote:
           | If you are seeing 1% of operations failing, I can guarantee
           | you are Doing It Wrong.
        
           | CyberDildonics wrote:
           | All of this hinges around the global lock, although I'm not
           | sure what you mean exactly. Are you talking about memory
           | allocation, a lock that exception handling takes or something
           | else?
        
             | tneumann wrote:
             | The main problem is the unwinding lock. Memory allocation
             | is a bit unfortunate, too, but much less so.
             | 
             | Note that I am trying to fix the unwinding problem, I have
             | submitted a patch to libunwind to eliminate the contention
             | during unwinding:
             | 
             | https://reviews.llvm.org/D120243
             | 
             | It require application support, unfortunately, as there is
             | currently no way that libunwind can figure out if a shared
             | library has been added or removed. But if you are willing
             | to indicate that from within the application the
             | performance problem is mostly fixed. The memory allocation
             | issue remains, but I can live with that. I cannot live with
             | single threaded unwinding.
        
               | drmeister wrote:
               | Thanks for that patch - I'll watch this with great
               | interest.
               | 
               | Is there a problem if dynamic libraries invoke
               | dlopen/dlclose they also need to call the sync function -
               | correct?
               | 
               | I'm asking because we've developed a Common Lisp
               | implementation that interoperates with C++ and it uses
               | exception handling to unwind the stack
               | (https://github.com/clasp-developers/clasp.git). We hit
               | this global lock in unwinding problem a lot - it causes
               | us a lot of grief.
        
           | zwieback wrote:
           | You mention the problem of having to recompile for ABI-
           | breaking improvements. Just out of curiosity - in the
           | environment you're describing, how much of the code cannot be
           | rebuilt? As I was reading I was assuming that most situations
           | where exception handling causes performance issues are
           | probably also situations where you have most if not all the
           | source code available. This wasn't the case in my formative
           | C++ years when 3rd party libs were expensive and distributed
           | as binaries (on floppies).
        
           | wvenable wrote:
           | > and 1% of your tasks fail
           | 
           | That's a lot of failures. I'd question whether or not you
           | should be using exceptions for something that happens 1% of
           | the time all the time.
           | 
           | Admittedly you might not have any choice in the matter if
           | it's a dependency that is failing.
        
             | infogulch wrote:
             | The "exceptions should be exceptional aka rare" line sounds
             | aspirational, but I wonder how true it is if you survey the
             | general landscape of actual libraries.
        
               | IshKebab wrote:
               | Probably not that rare to use exceptions but I imagine it
               | is _very_ rare to use exceptions for normal flow control
               | - that is, to continue execution after an exception. It
               | 's a common pattern in Python but not C++.
               | 
               | I've done it exactly once and I ended up removing it
               | because it makes debugging exceptions that you care about
               | really annoying.
        
               | jolmg wrote:
               | > but I imagine it is very rare to use exceptions for
               | normal flow control - that is, to continue execution
               | after an exception
               | 
               | Exceptions are at least a very central part of the normal
               | flow control of parser combinator libraries.
        
               | gpderetta wrote:
               | You are not wrong. The exceptional exception guideline
               | feels circular. I would use exceptions significantly more
               | if I could rely on their performance.
        
           | Asooka wrote:
           | I agree with the premise - C++ exceptions are not a good
           | choice for signalling numerical errors in high-performance
           | computing environments, where said errors can happen with
           | some frequency.
           | 
           | However, I do not agree with the conclusion - what is the
           | difference between removing exceptions and simply not using
           | them in your project? All compilers already let you compile
           | code without exceptions and the product I work on compiles
           | all numerical code that way. I don't see what benefit you
           | expect to reap from changing the fundamental way C++
           | exceptions are implemented and work. At the high end, you
           | want a custom error solution fit for your particular task, I
           | don't think we can have a generic exception framework that
           | will work for every high-performance computing project.
        
           | cesaref wrote:
           | I've worked in low latency trading, and with large
           | distributed back testing setups with hundreds of high core
           | count machines, and not once felt the need to disable
           | exceptions, or for that matter, felt the impact of them
           | happening. A fair bit of noexcept stuff was useful to improve
           | runtime performance, but that was about it.
           | 
           | I would suggest you need to address that 1% of failing tasks
           | and determine what the issue is, as frankly, you are solving
           | the wrong problem. If I might suggest a solution, it sounds
           | like you are using threading when process farms might be a
           | better solution.
           | 
           | (and if i'm way off the mark with my suggestion, apologies,
           | i'm trying to help and have little information to work with).
        
             | CyberRabbi wrote:
             | Low latency is not the issue per se. It's more like being
             | able to guarantee a worst case upper bound runtime. Maybe
             | your system can tolerate indeterminate but rare worst case
             | run times but many useful systems cannot. E.g. flight
             | control systems but also ideally any interactive system.
        
             | nemothekid wrote:
             | I'm not sure I understand the appeal of C++ exceptions. For
             | years I've been told that Rust and Go's lack of advanced
             | exception support is a crutch; and that error returns or
             | sum types were unimaginative.
             | 
             | Now, in this thread, exceptions are supposed to be used
             | rarely. I don't see the difference between an exception and
             | Rust/Go's `panic`. If the error is rare enough, then
             | chances are you cannot gracefully recover. If the error
             | isn't rare; then why are you using exceptions for control
             | flow?
        
           | xienze wrote:
           | > I run massive parallel data processing tasks on machines
           | with 128 cores
           | 
           | So, not to be rude here, but this may be a real-world
           | scenario in your world, but not in everyone else's. I get the
           | sense the kind of stuff you're working on would benefit from
           | things like using assembly as well. But for desktop apps,
           | games, compilers, other command line tools... who cares about
           | the performance of throwing exceptions?
        
             | jgod wrote:
             | Games cares about it. But you're right that it's only very
             | high-performance contexts.
        
           | kfitch42 wrote:
           | Not only is this relevant to HPC environments, it also
           | impacts the other end of the spectrum in the embedded space.
           | When dealing with real-time requirements, you are much more
           | concerned with the worst case performance as opposed to the
           | average case or happy path performance.
           | 
           | This article makes it very clear that exceptions complicate
           | analysis of performance. The non-local nature of exceptions
           | mean I can't analyze performance in isolation. E.g. I can
           | test that thread 1 always meets its deadlines (even with
           | exceptions being thrown), then I can test that thread 2
           | always meets its deadlines.... but if thread 1 and thread 2
           | happen to throw exceptions at nearly the some time I might
           | miss both deadlines. Who knows, maybe this means a thruster
           | fires too long and that insertion burn fails ... and Mars has
           | a new crater instead of a lander.
           | 
           | And, once you throw -fno-exceptions you are no longer using
           | standard C++, which the standard library assumes. So, using
           | anything that would throw exceptions on memory allocation
           | failures is a no-go. You can work around this with extensive
           | use of allocators (that reference enough static memory to
           | avoid any possible out-of-memory situation)... but this is
           | not looking like idiomatic C++ anymore, and most off-the-
           | shelf libraries are unusable.
           | 
           | A completely local exceptions implementation (e.g.
           | Herbceptions) would solve this.
        
             | ncmncm wrote:
             | Using local allocators where they offer some benefit
             | certainly _is_ idiomatic C++. Freeing all objects so
             | allocated by simply reclaiming the blocks from a local
             | allocator is also idiomatic C++.
             | 
             | Major subsystems that use no memory except what is passed
             | in from above is also idiomatic C++.
             | 
             | C++ is a big tent. Things you do routinely in one part of a
             | program, such as at startup, may be very different from
             | what you do in a main loop, or in termination cleanup.
             | Things my program does may be very different from what your
             | program does.
        
       | rr808 wrote:
       | I've never liked exceptions, its always awkward combination with
       | returning null or throwing exceptions, or error codes. It always
       | ends up breaking some abstraction making it leaky - eg read_doc
       | throws a file exception, or web exception or higher level
       | exception that has no useful detail.
       | 
       | I'm really heartened in the last few years that FP popularity has
       | caused more people to avoid exceptions and even golang doesn't
       | support them.
        
       | Dork1234 wrote:
       | It would be interesting to see a breakdown of exception overhead
       | for C++/Rust/Haskell/Julia/Swift in multi-threaded workloads.
       | 
       | Sometimes that happens rarely but blocks many threads is a huge
       | pain to deal with.
        
         | lanstin wrote:
         | Just write code with a bunch of threads that are all logging a
         | few M / second and see if the CPUs are running equally hot or 1
         | is 100% and the rest are mostly idle.
        
       | gumby wrote:
       | I find this paper quite unconvincing.
       | 
       | Exceptions are _exceptional_ so in principle it doesn 't matter
       | (within reason) how long it takes to throw one as long as it
       | costs nothing not to do so. So measuring the cost of repeated
       | throws IMHO doesn't cast light on any useful case, and the
       | approaches that add runtime cost for the path not taken, even
       | Herb Sutter's, are not acceptable.
       | 
       | His code transformation example is simply the compiler behaving
       | properly, unless it can't make that transformation even when
       | foo() is declared noexcept.
       | 
       | The high core count is a real issue and a legitimate reason for
       | an ABI break. Exceptions are the kind of below the surface
       | plumbing that can't reasonably be implemented in regular code.
       | 
       | And in that regard the paper does make a good suggestion, though
       | it then dismisses it! The tree approach described in section 3.4
       | seems like the right kind of fix.
       | 
       | Ultimately there's a spectrum of branching (`return` -> `break`
       | -> `goto` -> `throw`) all of which need consideration in light of
       | multicore deployment.
        
         | JoeAltmaier wrote:
         | But there's a design pattern where nearly everything is
         | returned as an exception. It's the normal path in some code.
         | 
         | I deplore that - its side-effects writ large. But those that
         | use it, find it reasonable and sensible.
         | 
         | It's become hard to distinguish right and wrong when it comes
         | to CPU optimization. Different versions of the 'same' CPU can
         | have wildly different sweet spots.
        
           | gumby wrote:
           | > But there's a design pattern where nearly everything is
           | returned as an exception.
           | 
           |  _shudder_ The worst of cargo cults!
        
           | [deleted]
        
           | josephg wrote:
           | > But there's a design pattern where nearly everything is
           | returned as an exception. It's the normal path in some code.
           | 
           | The problem isn't that C++ exceptions are slow when used the
           | way the compiler expects. The problem is a mismatch between
           | how C++ expects you to use the feature, and how people are
           | actually using the feature.
           | 
           | I can't find the story now, but I read about this happening
           | with Ruby on Rails. Someone dug into why rails was so slow at
           | Twitter, and found the way rails was looping through an array
           | was that it would loop unbounded, and catch and discard the
           | array out of bounds exception at the end of the loop.
           | Whenever Ruby throws, it allocates 10k of memory and fills it
           | with all sorts of information for debugging - including a
           | full stack trace. And it was doing this work in the
           | background every time someone iterated through an array.
           | Fixing this resulted in a massive speed up at Twitter, and
           | presumably across the entire rails ecosystem.
           | 
           | I saw the same thing at (big tech company) a decade or so
           | ago. I was working on a project which used GWT. We brought
           | some people in to help us optimize, because the program was
           | too slow. The first thing they found was that the JS VM was
           | spending most of its time in the exception handler for some
           | reason. Turned out one of our engineers had a habit of using
           | throw & catch as a way to do multi level returns in complex
           | code in Java. But the exception was being converted to a
           | javascript exception by GWT. And javascript exceptions are
           | (were?) super slow.
           | 
           | Y'all gotta stop using exceptions like that. I know it feels
           | clever. But in most programming languages, using exceptions
           | for control flow will kill your performance.
        
             | LightMachine wrote:
             | > Whenever Ruby throws, it allocates 10k of memory and
             | fills it with all sorts of information for debugging -
             | including a full stack trace. And it was doing this work in
             | the background every time someone iterated through an
             | array.
             | 
             | Jesus
        
           | gumby wrote:
           | > It's become hard to distinguish right and wrong when it
           | comes to CPU optimization. Different versions of the 'same'
           | CPU can have wildly different sweet spots.
           | 
           | Indeed, though I am shocked when people post benchmarks in
           | which they are obviously unaware of this issue. It's why
           | compilers have all those architecture switches.
           | 
           | If I understand your comment correctly: in the case of the
           | posted paper, the compiler optimization issue the author
           | mentioned was a semantics issue, not an architecture issue.
           | If your comment was about optimizing for the multiprocessor
           | case, I apologize.
        
             | JoeAltmaier wrote:
             | You're right, the issue is convoluted and nuanced. There
             | are architecture issues, cache sizes and layers to
             | consider, cost of misses and writes, alignment and bus
             | sizes.
             | 
             | Oh for the good ol days when register size was all you had
             | to think about!
        
           | NoSorryCannot wrote:
           | Exceptions have some of the properties of a discriminated
           | union, which is why they're used this way. Sometimes it seems
           | parsimonious compared to the alternatives.
        
         | cryptonector wrote:
         | How exceptional exceptions really are depends on the code being
         | run.
         | 
         | The serialization of stack unwinding issue is a real serious
         | problem.
        
           | gumby wrote:
           | > How exceptional exceptions really are depends on the code
           | being run.
           | 
           | Well yes, but EH is basically trying to be the best way to do
           | a highly non-local goto with a dynamic target. If that part
           | is your bottleneck than indeed you may have a more
           | specialized requirement.
           | 
           | I do on occasion build a homemade version of a standard
           | library datatype because I have a specialized need and only
           | need implement the subset our code will call. That doesn't
           | invalidate the more general implementations in the standard
           | library (which tend to be quite good, even corner cases).
           | 
           | Likewise sometimes I check for a null pointer being returned
           | or other local error flag. That doesn't mean I don't think
           | exceptions are a good idea.
           | 
           | > The serialization of stack unwinding issue is a real
           | serious problem.
           | 
           | That is the important part of the paper and as I wrote in my
           | comment, I am disappointed that a possible solution, written
           | and tested by the paper's author, was dismissed.
        
             | cryptonector wrote:
             | I agree. I'm not sure breaking the ABI to fix the unwinder
             | serialization issue should be a show-stopper, considering
             | that there still isn't a C++ ABI.
        
               | gpderetta wrote:
               | There are platform specific ABIs and there have been for
               | many years. Many platforms want to preserve the ABI at
               | all costs.
               | 
               | In any case this doesn't seem to be an ABI issue
        
               | cryptonector wrote:
               | I would think it shouldn't be an ABI issue, but maybe
               | part of the unwinding code gets statically linked into
               | shared objects?
        
               | gumby wrote:
               | Th problem is that the corpus of deployed libraries is a
               | _de facto_ ABI. Even with dylib versioning, catching the
               | case of API version is hard.
               | 
               | I think it could be done by changing the mangling
               | algorithm (basically: compile fails if new-ABI versions
               | are not available) but I haven't thought enough about it
               | to remove the words "I think" from the beginning of the
               | sentence.
        
         | gpderetta wrote:
         | I'm certainly not an expert but as far as I understand, fixing
         | the exception global lock need not break the ABI on gcc/glibc.
         | See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71744
        
           | gpderetta wrote:
           | Turns out I need to read more carefully. Improvements can be
           | done in a non-abi breaking way, but more radical changes are,
           | according to the authors ABI breaking.
        
         | randyrand wrote:
         | The only way exceptions should be _exceptional_ is that they
         | should rarely be used in any code base. At best, the are a
         | micro optimization that helps you a tiny bit in the success
         | case. They should be used when standard return value errors are
         | measured to be impacting perf (ie. exceedingly rarely).
         | Unfortunately, C++ language authors made them basically a
         | requirement for OOP and RAII.
         | 
         | Imagine writing a parser. Can you use exceptions? It's hard to
         | say! In fact, often impossible to say! How often an exception
         | is thrown is highly dependent on the input data.
         | 
         | So now what? No one likes code that has 2+ ways of indicating
         | method failure. Especially when those 2 ways of failure
         | reporting don't compose well. It's unnecessary complexity! If
         | you are a library author, do your users need to check for both
         | Exceptions and Error codes?
         | 
         | A standard way of indicating function failure is more important
         | than the micro optimization exceptions give us, especially
         | considering exceptions often inadvertently destroy perf.
         | 
         | If you want to use exceptions, they should be used in very
         | limited scope when perf measurements indicate they would help,
         | tightly wrapped in a catch, and then immediately converted to
         | some other standard error type.
         | 
         | Even better, we should just let the choice of when to use
         | exceptions up to an optimizing compiler, and provide the
         | compiler a function to convert between exceptions and our more
         | general purpose error type of choice.
        
         | talaketu wrote:
         | "exceptions are exceptional" means what?
        
           | pavon wrote:
           | In C++ the convention is that exceptions shouldn't be used
           | for things you expect to happen in the normal execution of
           | the program, in a way that would harm performance. For
           | example, it is better to explicitly check if an item is in a
           | map than to rely on exception handling to branch to the case
           | where the item doesn't exist. Generating an exception for
           | FileNotFound would be fine for a single file selected by the
           | user in a UI, but you'd probably avoid it if checking for the
           | existence of a large number of files based on a pattern. Most
           | exceptions should either be a bug, or exhaustion of
           | resources.
           | 
           | This is in contrast to say python where the convention is to
           | rely heavily on exceptions as part of the normal flow of the
           | code, sometimes described as "asking forgiveness, not
           | permission". It is not uncommon for a method argument to
           | support multiple types, and to discern them by treating the
           | object like one type and if you get an exception, then try
           | treating it like another type. Likewise, if you're not sure
           | an item is in a dict, you just try to access it, and catch
           | the exception if it isn't. This has performance impacts, but
           | so does everything else about python, so it isn't worth
           | optimizing.
        
           | Jtsummers wrote:
           | Exceptional means "unusual" (by the dictionary definition).
           | In theory, exceptions should be thrown in in exceptional
           | (rare, unusual) circumstances. Things like running out of
           | memory, or dealing with random poorly constructed data inputs
           | are reasonable circumstances to use exceptions. However, if
           | your input is consistently mangled, an exception may not be
           | the appropriate way to handle it since it becomes a normal
           | thing (if for no other reason than performance) depending on
           | how you want to handle the problem and whether that
           | performance cost is worth it.
        
           | gumby wrote:
           | The reason they care called "exceptions" is they are not part
           | of the normal behavior of the function (/block, algorithm)
           | and aren't something that can be handled locally. Something
           | that is exceptional is unusual, out of the typical scope of
           | things. In English there is a phrase, "the exception to the
           | rule" -- because the rule is what normally happens.
           | 
           | So if you are trying to hold a lock you don't throw an
           | exception, you just wait and try again. Perhaps you can't
           | reach that host; try again a few tiles before giving up and
           | throwing an exception. But if you try to write to removable
           | media and the device won't open, all, your program isn't
           | going to mount a tape itself: throw an exception and let the
           | problem be handled at a higher level.
        
           | jcelerier wrote:
           | It means that in normal usage conditions, if you install your
           | software on a clean computer and run it and nothing weird
           | happens outside of your program, then no exception should
           | ever be thrown.
        
           | timClicks wrote:
           | They're rare. Therefore their runtime costs have little
           | impact on the overall running time. At least that's my
           | understanding of the argument.
        
         | shrimpx wrote:
         | The problem is that in many practical situations you don't know
         | which situation is "exceptional".
         | 
         | If you write a jpeg-processing service, it's intuitive to raise
         | an exception on a malformed jpeg, but there's no guarantee that
         | only 1% of the jpegs users upload to the service are malformed.
         | 
         | In other words, we treat exceptions as exceptions from _our
         | code expects_ , not what's statistically unlikely in the input
         | space, which is in many cases impossible to predict with
         | accuracy. (E.g., even if only 1% of your inputs are malformed
         | over all time, on some Thursday you may be hit with 80% bad
         | inputs, making the performance drop across the service
         | unacceptable.)
        
           | fivea wrote:
           | > If you write a jpeg-processing service, it's intuitive to
           | raise an exception on a malformed jpeg (..)
           | 
           | Not really. Having to parse a malformed doc is not an
           | exceptional situation: it's a basic use case, and one which
           | is very close to the happy path.
        
             | shrimpx wrote:
             | Your opinion being that you should _not_ use exceptions in
             | that type of case. In which cases should you use
             | exceptions?
        
               | gumby wrote:
               | My experience in using exceptions (and restartable
               | conditions) over the last 40 years is that exceptions are
               | for things you don't have the ability or "knowledge"
               | (i.e. state) to handle locally.
               | 
               | So a function that ingests a file and processes it may
               | throw an exception if the file isn't found so that the UI
               | can catch it and ask the user for an alternative filename
               | (or to give up and not open a file at all).
               | 
               | If you're connecting to a remote machine and don't get a
               | response, you might throw an exception because you don't
               | know if the user typed the name wrong.
               | 
               | While if you are already talking to a machine and it
               | stops responding it's reasonable to wait a moment and
               | retry, as if could be a transient network brown-out which
               | is something you can deal with on your own.
        
               | whatshisface wrote:
               | When something happens that violates your assumptions
               | about your own program's behavior, throwing it into a
               | state where it doesn't know what happens next. Kind of
               | like a panic.
        
               | foldr wrote:
               | This would mean that attempting to open a file that
               | doesn't exist shouldn't throw an exception. But that is
               | exactly what it does in the standard libraries of many
               | languages with exceptions.
        
               | spc476 wrote:
               | It should be up to the application to throw or not, not a
               | library. I write a system service. If it can't find the
               | configuration file, it can't continue, so it throws an
               | exception. If it can't open a file that contains state
               | from a previous run (maybe because it's the first time
               | it's running) that's fine, the program can run without it
               | and thus, no exception.
        
               | jcelerier wrote:
               | Not in C++ I believe ?
        
               | foldr wrote:
               | The C++ standard IO library doesn't enable exceptions by
               | default, but IIRC that's just a relic of the fact that it
               | dates back to when C++ didn't have exceptions.
        
               | mpyne wrote:
               | It's library dependent. iostreams (part of C++ std lib)
               | doesn't throw exceptions by default but I believe it can
               | be configured to throw if you want.
        
       | zmmmmm wrote:
       | It's unclear to me if the author is saying that the global mutex
       | for unwinding the stack interferes with the non-exception-
       | throwing code paths, or are they just saying that the exceptions
       | themselves bottleneck and are so inefficient that this becomes a
       | significant impact on overall throughput?
       | 
       | It does seem like at least in theory it should be possible to
       | create a non-locking / blocking exception unwinder, if there is
       | no actual contention between the threads. If that can be done
       | then it seems like the solution should be to do that rather than
       | abandon a whole language feature. This is a bit like the Python
       | GIL question. I would say if the language spec means you have to
       | have a "GIL" in any context in a high performance language like
       | C++ then it ought to be addressed at the spec level.
        
       | rossmohax wrote:
       | If not exception, then how to fail constructor?
        
         | CyberRabbi wrote:
         | The "Herbceptions" proposal addressed this. http://www.open-
         | std.org/jtc1/sc22/wg21/docs/papers/2018/p070...
        
         | synergy20 wrote:
         | you can still set a static flag or something in ctor then check
         | it, but you can't do anything about dtor indeed if you need
         | throw there.
        
           | Koshkin wrote:
           | Except you are not supposed to throw in a destructor.
        
         | worik wrote:
         | It has been many years since I used C++
         | 
         | But returning null is an obvious choice.
         | 
         | Combined types which return the structure or an error are
         | another. Has the advantage of returning error information, why
         | failure.
         | 
         | There are many ways
        
           | roger10-4 wrote:
           | You can't return null from a constructor (constructors have
           | no return)
        
           | Calavar wrote:
           | > But returning null is an obvious choice.
           | 
           | It's not because C++ is not an everything-is-a-reference
           | language.
           | 
           | > Combined types which return the structure or an error are
           | another. Has the advantage of returning error information,
           | why failure.
           | 
           | Sum types are great, but changing constructors to return sum
           | types would break all existing code. It would also lead to a
           | whole slew of questions about how the construction of arrays
           | of values would work. (Does an array of an objects now turn
           | into an array of wrapper types? Byebye SIMD optimization!) It
           | would also break the consistency of constructing POD vs non
           | POD types, which would make writing templates that need to
           | generalize over both a huge PITA.
        
         | fpoling wrote:
         | Signal failure via an out parameter reference. For destructors
         | store the reference in the object and call an error method on
         | errors.
        
           | ncmncm wrote:
           | That is the worst of all possible choices.
           | 
           | Just throw.
        
           | mannykannot wrote:
           | I think that would be problematical in those cases where the
           | special constructors are called, and complicate using
           | constructors in expressions and argument lists.
        
         | _b wrote:
         | Don't write constructors that can fail unless it is failure
         | that would be appropriate to crash for. That works out a lot
         | better than it might naively sound. It is hard for people to
         | reason about the possibility of constructors/destructors
         | failing, so actually rather nice to just forbid it.
        
           | addaon wrote:
           | And for those super-rare cases where it's appropriate to
           | crash / fail hard in a constructor but you still have
           | requirements about reporting or even recovery...
           | setjmp/longjmp still exist.
        
             | ncmncm wrote:
             | You have always had the choice available to do something
             | overwhelmingly worse than throwing. That is not a reason to
             | do it.
        
           | throwaway5486nv wrote:
        
       | Thaxll wrote:
       | That's why we disable exceptions in video games.
        
         | Negitivefrags wrote:
         | We disable exceptions in video games due to lock contention
         | when throwing exceptions on multiple threads in high core count
         | situations?
         | 
         | No.
         | 
         | We disable exceptions in video games for dumb historical
         | reasons that no longer apply.
        
           | Thaxll wrote:
           | Dumb? https://www.youtube.com/watch?v=GC4cp4U2f2E
        
             | pjmlp wrote:
             | Depends on the point of view.
             | 
             | https://youtu.be/6hC9IxqdDDw
        
         | interroboink wrote:
         | For AAA / high-performance / console / close-to-the-metal video
         | games, at least.
         | 
         | There are tons of games where exceptions are perfectly fine.
         | Though still more often used for "probably going to crash soon"
         | situations than otherwise, I'd wager.
        
           | CyberRabbi wrote:
           | Since exception handling in desktop runtime environments
           | usually takes an unbounded amount of time, it's not good
           | practice to use them in your main loop since you have to
           | guarantee a new frame at 60Hz.
           | 
           | You can definitely do it but you'd be conceptually allowing
           | for frame skips in your codebase. It could be difficult to
           | audit and remove this assumption if down the road you wanted
           | to tighten up your main loop code.
        
             | interroboink wrote:
             | Agreed about main loop -- that requires special care.
             | 
             | But there's often lots of other stuff going on, such as IO
             | in other threads. You wouldn't want that stuff in your main
             | loop for the same reasons, so since it's segregated anyway,
             | exceptions aren't so bad (usually).
        
         | alfazaleng wrote:
        
       | worik wrote:
       | Begs the question: Why C++?
       | 
       | Legacy projects (I am not being pejorative, they are important)
       | which must be maintained aside, should not C++ be deprecated?
       | 
       | We have many new languages, we always had C. What does C++ give
       | us in 2022 that makes up for the enormous cognitive load of
       | understanding and keeping up with it.
        
         | trinovantes wrote:
         | I still use C++ for the occasional projects that a quick Python
         | script can't easily solve
         | 
         | - I've been using C/C++ since my first year of undergrad so I'm
         | already familiar/comfortable with it
         | 
         | - Java has too much boilerplate (my mind is still stuck in Java
         | 8 so maybe things have changed since then)
         | 
         | - I found myself spending more time fighting the Rust/Go
         | compiler than actually solving my problems
         | 
         | - I don't want to package a web browser for a simple 1-2 UI
         | application
        
         | ska wrote:
         | > We have many new languages, we always had C.
         | 
         | The short answer is probably that (unsurprisingly) C still does
         | not scratch the particular itches that C++ was created for, and
         | non of the new languages hit all of them well either. Plus
         | network effect.
        
         | Koshkin wrote:
         | Little things... Destructors; overloading; namespaces; and yes,
         | exceptions. Things I can no longer live without. Generics
         | (templates) are nice to have, too.
        
         | criddell wrote:
         | Believe it or not, there are a lot of us who enjoy C++ and
         | think the language is getting better all the time. IMHO, it's a
         | pretty good time to learn C++.
         | 
         | As for what do you get? Well, compared to C you can get higher
         | programmer productivity and compared to any language other than
         | C, you get great performance.
        
           | addaon wrote:
           | I find that it's not rare to get better performance from C++
           | than C. As a trivial example, generic container code in C is
           | likely to be run-time generic and sit on memcpy etc; the same
           | functionality in idiomatic C++ is likely to be compile-time
           | generic and be able to use fixed-size copy/move operations
           | instead.
        
         | freedomben wrote:
         | C++ is still a wonderfully performant language, and there are
         | still domains where it is clearly a leader (networking,
         | games/graphics, etc). Rust is slowly displacing it but there's
         | still a lot of road left. The maturity, stability, prior art
         | are also worth something. The pitfalls are really not _that_
         | big and dangerous, although to someone who doesn 't do C++ I
         | can see why it has that perception. Additionally, if you aren't
         | a functional programming lover (like I've become lately), C++
         | is one of the funnest languages to work in (as long as the
         | codebase follows could principles).
         | 
         | That said while I used to use C++ for nearly everything, these
         | days I use Elixir for app dev whenever I can, and Ruby and bash
         | for scripts. However if I were going to write a desktop app
         | today I'd most likely go for C++ so I could use Qt. I do really
         | need to try out new GTK though, sounds like it's gotten really
         | great.
        
           | ncmncm wrote:
           | More people start using C++ professionally in any given week
           | that the sum total paid to code Rust.
           | 
           | Rust is not "displacing" C++ anywhere beyond the HN echo
           | chamber.
        
             | lanstin wrote:
             | We've talked about Rust in my monthly beering meetings with
             | other programmers, so it's definitely gaining mindshare to
             | some extend. And it's being prepped for Linux kernel
             | inclusion. Once that lands it will be pretty respectable.
        
         | physicsguy wrote:
         | If you don't want to use LLVM then you don't have much choice.
        
           | Koshkin wrote:
           | That's not true. GCC, for instance, is a GNU _compiler
           | collection_. (True, it does not include Rust, the last time I
           | checked.) Other than that, there 's always Java, Common Lisp,
           | Haskell, and D. Plenty of choice there.
        
         | AnimalMuppet wrote:
         | > What does C++ give us in 2022 that makes up for the enormous
         | cognitive load of understanding and keeping up with it.
         | 
         | If I were starting a greenfield project today, I might choose
         | C++ for it, depending on what it was. (Pick the best tool for
         | the job, and all that.) But if I picked C++, I would not use
         | the full enormity of the entire C++ language specification. I
         | would use the parts that helped with the program I was trying
         | to write, and explicitly _not_ use the rest of it.
        
       ___________________________________________________________________
       (page generated 2022-02-22 23:01 UTC)