Live data from Hacker News

Problems with C++ exceptions

marler8997.github.io

61–70 of 112 posts

Re: Problems with C++ exceptions

#61
You probably do want that exception to bubble up, actually. You probably don't want to catch it immediately after open. Because you need to communicate a failure mode to your caller, and what are you going to do then? Throw another exception? Fall back to error codes? Unwind manually with error codes all the way up? And if so, logging was the wrong thing to do, since the caller is probably going to log as well, based on the same philosophy, and you're going to get loads of error messages for one failure mode, and no stack trace (yes, there are ways of getting semi-decent stack traces from C++ exceptions).

Exception safety has a lot of problems in C++, but it's mostly around allowing various implicit operations to throw (copies, assignments, destructors on temporaries and so forth). And that does come down to poor design of C++.

Re: Problems with C++ exceptions

#62

Java and Rust are the only language that I know of that has proper handling of exceptions; mandatory declaration as part of method declaration, since exceptions ARE an integral part of the contract. (Yes, I consider the Result being corresponding to exception declaration, since the return value MUST be checked prior to use of T.)

Don't forget, failure modes pierce abstraction boundaries. An abstraction that fully specifies failure modes leaks its implementation.

This is why I think checked exceptions are a dreadful idea; that, and the misguided idea that you should catch exceptions.

Only code close to the exception, where it can see through the abstraction, and code far away from the exception, like a dispatch loop or request handler, where the failure mode is largely irrelevant beyond 4xx vs 5xx, should catch exceptions.

Annotating all the exceptions on the call graph in between is not only pointless, it breaks encapsulation.

Re: Problems with C++ exceptions

#63
This strikes me as all wrong. The whole point of exceptions is control flow and destructors. By getting rid of RAII for the sake of simplifying the callsite a little, the author fails to obtain the real advantage, which is automatic resource unwinding of all local resources under failure conditions both during initialization and usage.

If you want to simplify the callsite, just move the exception handling to a higher scope. I can admit it’s a little irritating to put a try/catch in main but it’s trivial to automate, and most programs are not written inline in main.

The main problems I see with destructors have to do with hidden control flow and hidden type information. That said, hiding exceptional control flow from the mainline control flow of a function is also a useful feature, and the “exception type” of any given function is the recursive sum of all the exception types of any functions or operators it calls, including for example allocation. That quickly becomes either an extremely large and awkward flat sum or an extremely large and awkward nested sum, with awkward cross-namespace inclusion, and it becomes part of the hard contract of your API. This means, transitively, that if any deep inner call needs to extend or break its error types, it must either propagate all the way up into all of your callers, or you must recover and merge that error into _your_ API.

For _most_ usecases, it is just simpler to implement a lean common interface such as std::exception and allow callers who care to look for more detailed information that you document. That said, there is a proposal (P3166 by Lewis Baker) for allowing functions to specify their exception set, including via deduction (auto):

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p31...

Re: Problems with C++ exceptions

#64
post #57

This post completely misunderstands how to use exceptions and provides "solutions" that are error-prone to a problem that doesn't exist. And this is coming from someone that dislikes exceptions.

what about the misreported errno problem?

obviously the errno should have been obtained at the time of failure and included in the exception, maybe using a simple subclass of std exception. trying to compute information about the failure at handling time is just stupid.

Re: Problems with C++ exceptions

#65
post #3

Java and Rust are the only language that I know of that has proper handling of exceptions; mandatory declaration as part of method declaration, since exceptions ARE an integral part of the contract. (Yes, I consider the Result being corresponding to exception declaration, since the return value MUST be checked prior to use of T.)

Swift user here: I have to say one of the best features of Swift is the exception handling. Which is to say, exceptions in Swift are not C++/Java/Obj-C style exceptions, but instead are a way to return an error result from a function. And Swift enforces that the error is handled. That is, a `throw` statement in Swift simply returns an `Error` value to the caller via a special return path instead of the normal result.…

that sounds very similar to noexcept(bool) to me, except that noexcept can be computed, for example by deriving from some generic trait, and we presume throws unless specified non-throwing by noexcept.

Re: Problems with C++ exceptions

#66
post #62

Java and Rust are the only language that I know of that has proper handling of exceptions; mandatory declaration as part of method declaration, since exceptions ARE an integral part of the contract. (Yes, I consider the Result being corresponding to exception declaration, since the return value MUST be checked prior to use of T.)

Don't forget, failure modes pierce abstraction boundaries. An abstraction that fully specifies failure modes leaks its implementation. This is why I think checked exceptions are a dreadful idea; that, and the misguided idea that you should catch exceptions. Only code close to the exception, where it can see through the abstraction, and code far away from the exception, like a dispatch loop or request handler, where t…

This is a better way of expressing what I had been thinking about putting exception details behind an interface, except that in my mind encapsulating errors is just good design rather than implementation hiding, since the programmer might want to express a public error API, for example to tell the user whether a given fopen failed due to not finding the file or due to a filesystem fault.

Re: Problems with C++ exceptions

#67
post #35

Earlier quoted context omitted.

It's already too exhausting to use for OOM, which is why Rust effectively punted on that from the very beginning. And the ironic thing is that anyhow::Error (or similar) seems poised to become idiomatic in Rust, which AFAIU always allocates, while the extremely belated try_ APIs... does anybody even use them? It's a shame. Were I designing a "low-level" or "systems" language, rather than put the cart before the horse…

I think Rust's choice to panic on OOM is the right choice for 95% of code people write. It would be absurdly verbose, and OOM can be very tricky to recover from without careful top down design because it's very easy for error handling code to itself try and allocate (print/format a message) leading to a process abort. The mistake was not having the fallible APIs on allocating containers from the start, for the users…

you mean kind of like dynamically implementing std::exception? hmm.

Re: Problems with C++ exceptions

#68
post #35

Earlier quoted context omitted.

It's already too exhausting to use for OOM, which is why Rust effectively punted on that from the very beginning. And the ironic thing is that anyhow::Error (or similar) seems poised to become idiomatic in Rust, which AFAIU always allocates, while the extremely belated try_ APIs... does anybody even use them? It's a shame. Were I designing a "low-level" or "systems" language, rather than put the cart before the horse…

Hm, not sure how you'd do this. OOM error auto bubbling up sounds dangerous because the inner code might not leave things in a consistent state if it exits unexpectedly, so that makes sense to manually handle, but it's tedious. Rust at least has nice ? and ! syntax for errors, unlike Go where the error-prone error handling actually ruins the entire language.

If only there were some sort of automated idiom for cleaning up state under failure modes

Re: Problems with C++ exceptions

#69
post #61

You probably do want that exception to bubble up, actually. You probably don't want to catch it immediately after open. Because you need to communicate a failure mode to your caller, and what are you going to do then? Throw another exception? Fall back to error codes? Unwind manually with error codes all the way up? And if so, logging was the wrong thing to do, since the caller is probably going to log as well, based…

> you're going to get loads of error messages for one failure mode

> and no stack trace

That loads of error messages, meaning every layer describes what it tried to do and what failed, IS a user readable variant of a stack trace. The user would be confused with a real stack trace, but nice error messages serve both the user and the developer.

Re: Problems with C++ exceptions

#70
post #62

Java and Rust are the only language that I know of that has proper handling of exceptions; mandatory declaration as part of method declaration, since exceptions ARE an integral part of the contract. (Yes, I consider the Result being corresponding to exception declaration, since the return value MUST be checked prior to use of T.)

Don't forget, failure modes pierce abstraction boundaries. An abstraction that fully specifies failure modes leaks its implementation. This is why I think checked exceptions are a dreadful idea; that, and the misguided idea that you should catch exceptions. Only code close to the exception, where it can see through the abstraction, and code far away from the exception, like a dispatch loop or request handler, where t…

If your error codes leak the implementation details through the whole call stack you are doing it wrong. Each error code describes what fails in terms of it's function call semantics. A layer isn't supposed to just return this upwards, that wouldn't make sense, but to use it to choose it's own error return code, which is in the abstraction domain of it's function interface.
Post reply on HN