Live data from Hacker News

Current hardware trends make C++ exceptions harder to justify

open-std.org

281–290 of 516 posts

Re: Current hardware trends make C++ exceptions harder to justify

#281

Earlier quoted context omitted.

> 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, gener…

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-…

"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.

Re: Current hardware trends make C++ exceptions harder to justify

#282

Earlier quoted context omitted.

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.

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…

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.

Re: Current hardware trends make C++ exceptions harder to justify

#283

Earlier quoted context omitted.

> 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.

It is in Go Lang which shares error handling philosophy with GP.

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

Re: Current hardware trends make C++ exceptions harder to justify

#284
post #191

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…

"exceptions are exceptional" means what?

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.

Re: Current hardware trends make C++ exceptions harder to justify

#285
post #167

Earlier quoted context omitted.

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 ar…

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.

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.

Re: Current hardware trends make C++ exceptions harder to justify

#286
post #262

Earlier quoted context omitted.

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.

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.

Not in C++ I believe ?

Re: Current hardware trends make C++ exceptions harder to justify

#287
post #191

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…

"exceptions are exceptional" means what?

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.

Re: Current hardware trends make C++ exceptions harder to justify

#288
post #191

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…

"exceptions are exceptional" means what?

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.

Re: Current hardware trends make C++ exceptions harder to justify

#289
> 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.

[Edit: It would seem the article does say something about how an rwlock is not feasible with the current implementation ... It doesn't sound terribly convincing]

Re: Current hardware trends make C++ exceptions harder to justify

#290
post #233

Earlier quoted context omitted.

> 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 p…

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.

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.

Post reply on HN