Live data from Hacker News

C++ Exceptions: Under the Hood (2013)

monkeywritescode.blogspot.com

71–80 of 116 posts

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

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

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.

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

#72
post #44

Earlier quoted context omitted.

No you don't, you're massively exaggerating. The standard library already has at least basic if not strong exception-safety all over it. And RAII is pretty darn standard practice and guarantees basic exception safety in your own code too. You don't need strong exception safety here, just basic is sufficient for most such cases. Go try this with std::sort (or std::adjacent_find or whatever) and tell me which of their…

Well but of course! These functions are already implemented with basic exception safety in mind. What if they weren't? This is exactly the same situation as a function that has callback(); which cannot be changed into if Err(error) = callback() { return error; } because that would break some invariants. Changing return type from "void" into some "result" is a mechanical change.

> Changing return type from "void" into some "result" is a mechanical change.

.. but then checking the returned val for error in every call sites is very far from mechanical change. (Attribute about unused return result can help here, with obvious drawbacks.)

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

#73

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

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 tried to create the file but the OS now says the abstract concept of files is alien to it" is an exception. You are not prepared for this eventuality, the best you can do is explain to the user as best possible what happened and hope a human knows what to do.

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

#74

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…

I guess people's experiences are different :-)

I find exceptions to be a perfectly fine error handling mechanism.

I certainly prefer it to cluttering my code with explicit checks for return codes and such like.

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

#75

Earlier quoted context omitted.

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.

What are the return values from a poisoned object’s methods? Does a poisoned vector have a poisoned integer as its size?

> What are the return values from a poisoned object’s methods?

That's up to you. You can do it as:

1. return a poisoned value

2. return a safe value, like `0` for its size

3. treat it as a programming bug, and assert fail

4. I know `null` is hated, but it is the ultimate poisoned value. Try to call a method on it, and you are rewarded with a seg fault, which can be considered an assert fail.

5. design your poisoned object to be a real object, with working methods and all. It can be the same as the object's default initialized state.

In other words, it's necessary to think about what the poisoned state means for your use case. I use all those methods as appropriate.

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

#76
post #31

Earlier quoted context omitted.

I actually agree that they technically aren't zero cost (notice I didn't even write that), but the cost is indirect. I've worked on a number of Java JITs and in practice, not a lot of hot code has catch blocks, and even when so, inlining is typically so deep that lots of exception edges (e.g. arising because a possible NPE) get optimized away. Most of the lost optimization opportunities are second-order costs, not fi…

Enregistering of variables is also lost, because to restore them during unwinding they have to be retrieved from the stack.

Not sure what you mean here, but generally Java JITs generally don't use callee-saved registers at all because they need precise stack maps for GC. So whatever small amounts performance they might lose here isn't due to exceptions.

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

#77
post #76

Earlier quoted context omitted.

Enregistering of variables is also lost, because to restore them during unwinding they have to be retrieved from the stack.

Not sure what you mean here, but generally Java JITs generally don't use callee-saved registers at all because they need precise stack maps for GC. So whatever small amounts performance they might lose here isn't due to exceptions.

> Not sure what you mean here

Allocating local variables into registers rather than assigning stack locations for them. Registers are faster than memory. EH unwinders restore the stack before jumping to the catch block, but not the register contents.

Stack maps wouldn't be necessary for non-pointers, like an integer variable. Stack maps also have their own performance problems, which is why D doesn't use them.

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

#78
post #45

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`?" I could image throwing an int when writing a shell utility and throwing the return value of main as an int but I suppose doing that usefully would be pretty rare. Usually one cares more about whether a shell utility is successful or not not so much about the precise reason it failed. So, while I could imagine doing that, I don't see myself going for that…

Yes, but you couldn't mix that code with code that throws an `int` for other porpoises. It becomes a global straightjacket for your code.

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

#79
post #26

Earlier quoted context omitted.

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

> Another option is to regard out-of-memory as a fatal error, not one that needs to be thrown and caught.

This is in practice almost invariably the case for large programs. Somebody (Herb Sutter maybe?) asked the major C++ Standard library implementers, and none of them really bothers to handle the tricky parts of this. If you write code to try to pre-allocate a 10TB vector of 'Z's you can probably get that to throw you the exception that you read about in the documentation, but if the library code for opening a file can't find 64 bytes for a temporary object they aren't going to bubble up an exception, they're going to crash your program and too bad.

If you write an operating system kernel, you care about running out of memory, if you write the embedded firmware for a jet engine, you care (actually you likely never allocate memory at runtime, so in that sense you don't care), but in both those cases you live in a world where many other problems are far above you out of sight, so you can afford to care about stuff like how much RAM there actually is. You don't want the C++ standard library down where you live, and they don't want your problems. Everybody who lives up above the C++ standard library doesn't care, which is why the people implementing the library don't care either.

Yes, all of Unicode processing should use U+FFFD (the replacement character). Not just UTF-8, if you have any reason to do anything Unicode related and you're in a state where other paths forward are nonsense, emit U+FFFD. Take XML. Because the people involved hated ASCII control codes XML says you can't express them in XML 1.0 (which you will in practice have to use). I don't mean they need to be escaped, I mean you intentionally cannot express them. So if you have some arbitrary ASCII text that might include control codes, you can't write that as valid XML. What to do? Emit U+FFFD whenever this problem arises. Your users go "Huh, my Vertical Tab turned into this weird character in the XML output" and you send them to talk to the XML committee which will tell them they're a sinner and must repent of the evil of Vertical Tab and now your user knows you aren't crazy and maybe they stop using XML or maybe they don't but either way your code works.

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

#80
post #76

Earlier quoted context omitted.

Not sure what you mean here, but generally Java JITs generally don't use callee-saved registers at all because they need precise stack maps for GC. So whatever small amounts performance they might lose here isn't due to exceptions.

> Not sure what you mean here Allocating local variables into registers rather than assigning stack locations for them. Registers are faster than memory. EH unwinders restore the stack before jumping to the catch block, but not the register contents. Stack maps wouldn't be necessary for non-pointers, like an integer variable. Stack maps also have their own performance problems, which is why D doesn't use them.

The term used most often for this is "spilling". I figured this is what you meant by "deregistering" but I wasn't sure, so I didn't want to assume.

> Registers are faster than memory. EH unwinders restore the stack before jumping to the catch block, but not the register contents.

I get that, which is why Java JITs don't use callee-saved registers. I mean, they use all the physical registers, of course, but their calling convention does not have callee-saved registers.

Post reply on HN