Live data from Hacker News

C++ Exceptions: Under the Hood (2013)

monkeywritescode.blogspot.com

81–90 of 116 posts

Re: C++ Exceptions: Under the Hood (2013)

#81
post #56

Earlier quoted context omitted.

I agree that a modern high-level programming language model needs an ergonomic error model. But C++ exceptions are not the only way to go. You can have error model that have similar (or even better ergonomy) than C++ while not having any of the drawbacks (like extremely complicated runtime stack, slow exception handling, messed up control flow etc.). Basically, in my personal opinion, any error handling that involves…

IMO algebraic data types pretty much solve everything that exceptions try to solve. while also encoding into the type system that it can fail/what failure modes there are, while also forcing you to handle it locally.

Local handling is, of course, an anti-pattern in code using exceptions, and not to be encouraged. It's rare that you handle exceptions; normally, you just abort what you're doing and unwind.

If you have an API which fails often enough that you want to handle exceptions from it, it probably shouldn't use exceptions, and use some kind of conditional result or ADT equivalent instead. A concrete example would be the TryParse methods in .NET.

Re: C++ Exceptions: Under the Hood (2013)

#82
post #10
post #4

Earlier quoted context omitted.

Doesn't GCC support multiple exception handling options?

I'm not sure exactly what you mean but there was a switch to DWARF EH ages ago (GCC 2?)

sjlj is still supported, at least on some platforms, e.g. MinGW.

Re: C++ Exceptions: Under the Hood (2013)

#83

Earlier quoted context omitted.

On the other hand ( ) they solve a handful of use-cases really really well. ( ) if you are writing relatively decent C++, most code is pretty much exception safe already ( ) lots of abstractions are dangerous when mis-used ( ) they are sufficiently low cost that they almost never show up in the fairly extensive perf profiling I do on a large real-world application (LibreOffice). And LibreOffice throws exceptions __a…

> LibreOffice throws exceptions __a lot__ They're terrible for this. C++ Exceptions are a perfectly good exception mechanism, but what C++ programmers are trained to do with them isn't exceptions but error handling and they're not suitable for that. "I tried to create the file but it already existed" is an error - and now you're going to write the unhappy path code, this is the wrong place to have exceptions. "I trie…

> error handling and [exceptions are] not suitable for that

I disagree :).

Exceptions are fundamentally equivalent to "return sum type" error handling pattern. In an exception-enabled environment, you can imagine every function returning some Foo is really returning a {Foo, Error}. Then, every call like below, outside of try/catch block:

  value = SomeFunction();
is secretly translated to:

  maybeValue = SomeFunction();
  if(!maybeValue) { return maybeValue.error(); }
You can devise analogous translations for code in try/catch blocks.

The fundamental difference between exceptions and an Expected/Maybe mechanism is that exceptions don't force you to be explicit about all those Maybe values. If you want to handle some Error three layers up in the call stack, you don't have to litter the intermediary layers with explicit Maybes everywhere.

(This is, unfortunately, also their drawback in typical implementations - the set of possible Error types in the hidden Maybes becomes effectively open-ended, when with explicit Maybes, it's constrained and visible in source code - and, perhaps more importantly, in the ABI.)

The other day I did an experiment - I wrote two equivalent pieces of nontrivial production code, one using C++ exceptions, and other using the tl::expected library. If you looked past the syntactic noise, they mapped almost 1:1 in terms of error handling and error recovery patterns.

Re: C++ Exceptions: Under the Hood (2013)

#84

Working with and implementing C++ exceptions for 30 years now, including implementing exception handling for Windows, DOS extenders, and Posix (all very different), and then re-implementing them for D, I have sadly come to the conclusion that exceptions are a giant mistake. 1. they are very hard to understand all the way down 2. they are largely undocumented in how they're implemented 3. they are slow when thrown 4.…

Who invented exceptions in the first place? Did they first appear in Java, encouraging C++ to imitate a java feature?

Re: C++ Exceptions: Under the Hood (2013)

#85
post #9

Earlier quoted context omitted.

That's the article I used when I implemented exceptions in an LLVM-based compiler, so it's applicable to more than just GCC.

Is your work public? If my article was useful, I'd love to have a look at what you did!

It's public, but I doubt it still compiles against recent LLVM versions! I started it 8 years ago to get a better understanding how features like classes & operator overload would work in a JS-like language. It was really fun!

https://github.com/castel/libcastel/blob/master/runtime/sour...

https://github.com/castel/libcastel/blob/master/runtime/sour...

I remember that at the time there were very few resources on personality functions, even in the LLVM doc - I had to make a lot of research before finding your articles, which were extremely helpful!

I got reminded of them yesterday after someone pinged me on a Stack Overflow answer I made at the time, asking for an updated link; after I found your long-form article I figured it would be a good topic for HN as well :)

https://stackoverflow.com/questions/16597350/what-is-an-exce...

Re: C++ Exceptions: Under the Hood (2013)

#86
post #52

Earlier quoted context omitted.

I'm experimenting with a solution in C3 that has this behaviour. I don't have a sum type as such, but the binding acts as one. I call the binding a "failable". int! a = getMayError(); // a is now either an int, or contains an error value. // foo(a) is only conditionally invoked. int! b = foo(a); // The above works as if it was written: // int! b = "if a has error" ? "the error of a" : foo("real value of a"); // A sin…

So you’ve built in monadic bind for the Either monad into the language: Right x >>= f = Right (f x) -- normal case Left y >>= f = Left y -- error propagation case (The slogan is “monadic bind is an overload for the semicolon”.) I don’t expect this knowledge will dramatically change what you’re doing, but now that you know that’s how some people call it you have one more place to steal ideas from :)

No I'm quite aware of this. It's a restricted, implicit variant of it. But not also that it's not the type but the binding, which makes it slightly different from using a `Result`.

Re: C++ Exceptions: Under the Hood (2013)

#87
post #81

Earlier quoted context omitted.

IMO algebraic data types pretty much solve everything that exceptions try to solve. while also encoding into the type system that it can fail/what failure modes there are, while also forcing you to handle it locally.

Local handling is, of course, an anti-pattern in code using exceptions, and not to be encouraged. It's rare that you handle exceptions; normally, you just abort what you're doing and unwind. If you have an API which fails often enough that you want to handle exceptions from it, it probably shouldn't use exceptions, and use some kind of conditional result or ADT equivalent instead. A concrete example would be the TryP…

I think that it is much better to return an adt with "common exceptional cases" and reserve exceptions for the truly exceptional ones. For example, a lost network connection shouldn't be one, but a put of memory one makes sense.

Local handling was meant in terms of locally seeing pitential errors

Re: C++ Exceptions: Under the Hood (2013)

#88

Working with and implementing C++ exceptions for 30 years now, including implementing exception handling for Windows, DOS extenders, and Posix (all very different), and then re-implementing them for D, I have sadly come to the conclusion that exceptions are a giant mistake. 1. they are very hard to understand all the way down 2. they are largely undocumented in how they're implemented 3. they are slow when thrown 4.…

> 10. has anyone yet found a legitimate use for throwing an `int`?

When an integer is the only value I need in the catch handler, I sometimes throw them, but only negative integers.

https://docs.microsoft.com/en-us/openspecs/windows_protocols...

Re: C++ Exceptions: Under the Hood (2013)

#89
post #86

Earlier quoted context omitted.

So you’ve built in monadic bind for the Either monad into the language: Right x >>= f = Right (f x) -- normal case Left y >>= f = Left y -- error propagation case (The slogan is “monadic bind is an overload for the semicolon”.) I don’t expect this knowledge will dramatically change what you’re doing, but now that you know that’s how some people call it you have one more place to steal ideas from :)

No I'm quite aware of this. It's a restricted, implicit variant of it. But not also that it's not the type but the binding, which makes it slightly different from using a `Result`.

Hm. OK. I tried writing a response several times but I still feel confused. Can you explain what you mean by “not the type but the binding”? Note that I know the Haskell but not the Rust (guessing from the “Result” name) way of working in this style.

(Not necessarily relevant or correct thoughts:

- Your language still seems to mark potentially-failed values in the type system, even if it writes them T! not Either Error T or Result;

- The way Haskell’s do-notation [apparently implemented as a macro package in Rust] is centred around name binding seems very close to what you’re doing, although it [being monadic, not applicative] insists on sequencing everything, so fails the whole block immediately once an error value occurs;

- Of course, transparently morphing a T-or-error into a T after a check for an error either needs to be built into the language or requires a much stronger type system; Haskell circumvents this by saying that x <- ... either gives you a genuine T or returns failure immediately, which is indeed not quite what you’re doing.)

Re: C++ Exceptions: Under the Hood (2013)

#90

Earlier quoted context omitted.

> LibreOffice throws exceptions __a lot__ They're terrible for this. C++ Exceptions are a perfectly good exception mechanism, but what C++ programmers are trained to do with them isn't exceptions but error handling and they're not suitable for that. "I tried to create the file but it already existed" is an error - and now you're going to write the unhappy path code, this is the wrong place to have exceptions. "I trie…

> error handling and [exceptions are] not suitable for that I disagree :). Exceptions are fundamentally equivalent to "return sum type" error handling pattern. In an exception-enabled environment, you can imagine every function returning some Foo is really returning a {Foo, Error}. Then, every call like below, outside of try/catch block: value = SomeFunction(); is secretly translated to: maybeValue = SomeFunction();…

Obviously we can translate one Turing complete paradigm into another, but that's not very interesting.

I argue that as so very often C++ the defaults are wrong. You can easily do the wrong thing, or you can go to a lot of effort to do the right thing, and since the right thing was technically possible C++ practitioners proudly declare C++ got this correct, and I say it did not.

[See also: everything about const from West Const being endorsed in NL26 despite being silly, through to the fact that the default is mutable for no good reason; the fact char isn't necessarily signed or unsigned you need to pick one if you care; need to explicitly use a provided replacement for the array type because the default built-in array type is broken; the default meaning of the literal "Hello, world" is this awful NUL-terminated byte array using that broken default array type; Way too many dubious implicit coercions, including narrowing conversions everywhere; I could go on]

Because we're not handling truly exceptional cases we will often want to treat the OK and error cases similarly. We tried to go outside and it was raining so maybe we should get an umbrella before venturing out again, but it wasn't on fire out there so we don't need to freak out and abandon our remaining plans to flee the fire immediately.

Exceptions make this needlessly difficult whereas sum types don't. The exception deliberately changes program execution, that is in fact its purpose, whereas the sum type lets you carry around the error and its context just as you would an "OK" result, until you need it for something or you decide you didn't need it and drop it on the floor.

Bad defaults get replicated for consistency. There are a lot of bad practices -- things you definitely shouldn't do -- that are now enshrined permanently in the ABI of the C++ standard library and so for consistency you're going to inherit those practices.

As a result I agree that Expected doesn't feel nicer in C++ today than exceptions but I argue that's a language defect, in a better language you'd find Expected worked better for the unhappy paths of your program and exceptions remained available for those truly exceptional cases that the programmer did not anticipate happening. Now, one programmer might feel that even "File already exists" truly is exceptional for their scenario, while another considers "Disk I/O error" to be merely an error they can cope with and no big deal (maybe the second programmer is writing an IT forensics program). That's going to vary, but the way for a standard library to reflect that is to use Expected almost everywhere and allow the developer who thinks "File already exists" is exceptional to throw for it, not have the standard library throw everything and then you race around trying to catch what you need to and hope you didn't miss anything.

Post reply on HN