Live data from Hacker News

C++ Exceptions: Under the Hood (2013)

monkeywritescode.blogspot.com

61–70 of 116 posts

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

#61

Earlier quoted context omitted.

So that sounds like the way invalid floating point operations give NaN, and then the NaN propagates everywhere. I've always found this super annoying because its often hard to figure out where the NaN comes from. Does your solution differ from this in a way that's less annoying?

The FPU does not include the source in the NaN, but that doesn't mean your own objects can't. What I do is have the error reported at the source, and then return the poisoned object. A better way would possibly be put the error message in the poisoned object, and report the error somewhere up the call stack.

I do that a lot with the firmware I write in C.

  typedef struct
  {
    err_t error;
    int error_line;
    char *error_msg;
    ...
    ...
  } thing_t;

  // set out of range error
  thing->error = THING_ERROR_OOR;
  thing->error_line = __LINE__;
  thing->error_msg = "outofrange"
You can grep on 'outofrange' and find where the error was set.

I originally started doing that to mark 'bad' analog readings in process control equipment. I wrote my filters and control loops to be able to 'eat' occasional bad readings without barfing. Worked very well.

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

#62

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

Forcing the programmer to manually write stack unwinding code is not a solution. That's like saying "garbage collecton is slow and complex, just use malloc() and free() instead".

Nobody said to manually write stack unwinding code or to only use malloc() and pair each of them with free().

There are other very good solutions that involve explicit structure. For example

- do not free things at all, just reserve a big chunk of address space and let the OS populate it as needed. When the process quits the OS frees everything automatically.

- do the same thing for parts of the program but implement the "OS part" in the program itself. There are variations of this known by terms such as "memory arena" or "pools". Basically, just take care to group allocations by end of lifetime. Then you can free everything in one go without tracking each lifetime individually in a stack frame (which is insane).

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

#63

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

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

Not sure if you consider this legitimate, but I have seen code that throws an errno.

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

#64
post #56

Earlier quoted context omitted.

Don't you need exceptions though? How do you terminate arbitrary operations without exceptions? Like say you call an algorithm (like std::sort) and during a callback (e.g. in the comparator) you decide to cancel the operation (perhaps user-requested). With exceptions it's easy; you just throw an exception and then catch it. No need to touch or even know the intermediate callers. But without exceptions what do you do?…

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…

Note you didn't really answer my question at all.

Right now lots of algorithms like std::search, std::find_if, etc. are not only exception-safe, but in fact exception-agnostic. Neither the algorithm, nor you, need to know a priori if your predicates will throw exceptions (which are things that may be literally impossible to know upfront), and yet despite that, (a) the algorithms will work completely correctly if any exception is thrown, (b) if you do need to do something like canceling the operations in the middle, you have a means to do that via exceptions, and (c) you will get extremely high performance as long as you don't throw an exception. That's a lot of flexibility even the most trivial implementations of many such algorithms get absolutely for free. (!) I don't know about you, but to me the fact that I can suddenly decide to "cancel" many functions halfway despite their authors never having to even think about that possibility is pure awesomeness.

So I asked "how would you do achieve {the benefits of the exception model} without exceptions" but you just said "it is possible" and... left me hanging. Well if that's really true, then how?

> You can have error model that have similar (or even better ergonomy) than C++ while not having any of the drawbacks

I don't buy it. Unless you're intentionally allowing yourself to introduce drawbacks that never existed in C++'s model. If you're really saying you can find a strictly better solution, then we're all definitely interested in hearing... and I'll believe it when I see it.

You have to realize ergonomicity (word?) isn't the only axis here. Performance is also a big one, and C++ is designed for maximizing performance in non-exceptional executions. I don't know what error models you're thinking of, but anything along the obvious stuff I've seen (like the usual "replace T with maybe/optional/fancy") would come with far greater performance hits even in the 'happy' paths than C++ has (not to mention potential increases in memory usage, etc. in more complex cases), and even their ergonomics would be debatable depending on the situation.

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

#65

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

Those look like problems for compiler implementers (tiny subset of users) or those writing code with very tight performance requirements (large amount of C++ code does not have such reqs). In spite of the reasons given, exceptions are successfully used (in C++ too) for error handling, because they can be much nicer that shuffling error codes/result types up the stack.

Really, as an end-user the issue with exceptions in C++ is another:

a) it's impossible to figure out what throws by looking at code.

b) it's (nearly) impossible to ensure that something doesn't throw

This means on one hand that one has to assume that any code can throw and manage resources appropriately, which is by now known and there are well-established idioms around it. On the other hand though it also means that the silliest error from a tiny library can bubble up into the event loop/main function and terminate an application.

Swift's syntax for exceptions illustrates what I mean, even though Swift does not unwind the stack.

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

#66
post #53

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

I have quit using exceptions in my own code, making everything 'nothrow'. Assuming not all code you use is your own, how does this work in combination with other code (like the STL) which is not nothrow?

Generic code (like you'd find in a library) is usually done with templates. Templates in D infer `nothrow`, giving them the advantage of being implicitly `nothrow` when their arguments are also nothrow. Inferring attributes this way is a major way D works.

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

#68
post #26

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

Can you describe your ideal error handling mechanisms? Or at least other mechanisms that feel more correct?

One technique I try first is to write code that cannot fail. For example, a sort function should never fail.

Consider the case of running out of memory. One option is to pre-allocate all the memory the algorithm will need, then it can't run out of memory. Another option is to regard out-of-memory as a fatal error, not one that needs to be thrown and caught.

Another example is UTF-8 processing. Early on, I did the obvious when invalid UTF-8 sequences were discovered - throw an exception. But this got in the way of high speed string processing (exceptions, even in the happy path, are slow). But what does one do anyway with such input? abort the display of the text? Nope. The bad sequence gets replaced with the Unicode "replacement character". This turns out to be common practice, and now my UTF-8 processing code cannot fail! And it's smaller and faster, too.

It's a fun challenge to figure out how to organize the program so it can't fail.

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

#69

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`?

I use that a lot in constexpr computations -- to stop the compilation, I usually do 'throw __LINE__'.

-- Using a more complex type is not warranted -- there is no catching end in constexpr.

-- And in case the same routine ends up called non-constexpr, it will be easy to identify the place that called 'throw' -- line numbers are unique without additional effort. Just don't put two throws on the same line.

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

#70
post #52

Earlier quoted context omitted.

I've read the proposals for it. It certainly looks good, yet exception handling looked good 30 years ago, too. I haven't used sum types myself, and often it takes years to discern whether things are really good ideas or not. What I personally use is the "poisoning" technique. This involves marking an object as being in an error state, much like a floating point value can be in a NaN state. Any operation on a poisoned…

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 :)

Post reply on HN