Live data from Hacker News

Problems with C++ exceptions

marler8997.github.io

91–100 of 112 posts

Re: Problems with C++ exceptions

#91

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.

I avoid using exceptions myself so I wouldn't be surprised if I misunderstand them :) I love to learn and welcome new knowledge and/or correction of misunderstandings if you have them. I'll add that inspiration for the article came about because It was striking to me how Bjarne's example which was suppose to show a better way to manage resources introduced so many issues. The blog post goes over those issues and talk…

RAISI is always wrong because the whole advantage of the try block is to write a unit of code as if it can't fail so that what said unit is intended to do if no errors occur is very local.

If you really want to handle an error coming from a single operation, you can create a new function, or immediately invoke a lambda. This would remove the need break RAII and making your class more brittle to use.

You can be exhaustive with try/catch if you're willing to lose some information, whether that's catching a base exception, or use the catch all block.

If you know what all the base classes your program throws, you can centralize your catch all and recover some information using a Lippincott function.

I've done my own exploring in the past with the thought experiment of, what would a codebase which only uses exceptions for error handling look like, and can you reason with it? And I concluded you can, there's just a different mentality of how you look at your code.

Re: Problems with C++ exceptions

#92
post #9
post #3

Earlier quoted context omitted.

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

I saw that in Swift, a method can declare it throws an exception, but it doesn't (can't) declare the exception _type_. I'm not a regular user of Swift (I usually use Java - I'm not sure what other languages you are familiar with), but just thinking about it: isn't it strange that you don't know the exception type? Isn't this kind of like an untyped language, where you have to read the documentation on what a method c…

I don't think it's strange at all--my main uses of the returned errors are

1a. yes, there was some error 1b. there was an error--throw another local error and encapsulate the caught error 2. treat result of throwing call as `nil` and handle appropriately

I don't think typed throws add anything to the language. I think they will result in people wasting time pondering error types and building large error handling machines :)

When I used Java, I found typed exceptions difficult to reason about and handle correctly.

Re: Problems with C++ exceptions

#93
post #3

Earlier quoted context omitted.

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

From what I can read Swift gives you a stack trace which is good. At the moment I’m using Go where that stack is only generated where the panic is triggered, which could be much higher up. Makes it a lot more unwieldy to figure out where an error happens because everyone uses: > if err != nil return err

This is built in to the language.

When you call code that can throw (return an error via the special return path) you either have to handle it or make the enclosing context also throwing.

Assuming `canThrow()`, a function that might throw an `Error` type:

    func canThrow() throws {
        ...
    }

Call canThrow(), don't handle errors, just rethrow them

    func mightThrow() throws {
        try canThrow() // errors thrown from here will be thrown out of `mightThrow()`
        ...
    }
Alternatively, catch the errors and handle them as you wish:

    func mightThrow() throws {
        do {
            try canThrow()
        } catch {
            ...handle error here
            ...or `throw` another Error type of your choosing 
        }
        ...
    }
There are a few more ways to handle throwing calls.. For example

- `try?` (ignore error result, pretend result was `nil`)

- `try!` (fail fatally on error result)

Re: Problems with C++ exceptions

#94
post #9
post #3

Earlier quoted context omitted.

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

I saw that in Swift, a method can declare it throws an exception, but it doesn't (can't) declare the exception _type_. I'm not a regular user of Swift (I usually use Java - I'm not sure what other languages you are familiar with), but just thinking about it: isn't it strange that you don't know the exception type? Isn't this kind of like an untyped language, where you have to read the documentation on what a method c…

Typed exceptions are unlike typed parameters or return values. They don’t just describe the interface of your function, but expose details about its implementation and constrain future changes.

That’s a huge limitation when writing libraries. If you have an old function that declares that it can throw a DatabaseError, you can’t e.g. add caching to it. Adding CacheError to the list of throwable types is an API breaking change, just like changing a return type.

Swift has typed errors now, but they shouldn’t be used carefully, and probably not be the default to reach for

Re: Problems with C++ exceptions

#95

My biggest beef with exceptions is invisible code flow. Add the attribute throws to function signature (so that its visible) and enforce handling by generating compiler error if ignored. Bubbling up errors is OK. In essence, this is result . Thats OK. Even for constructors. What I dislike is having a mechanism to skip 10 layers of bubbling deep inside call stack by a “mega” throw of type which none of the layers know…

Don't think of the uncaught type as a "mega" throw. It's just a distinct type of error that nobody specified that they can handle. If you truly worry about the caller missing something, then somewhere in there you can catch anything and translate into a recognizable exception. This is easiest to understand in a library. The interface functions can catch all and translate into one particular exception type for "unknown" or generic errors. Then, that will be caught by anyone using the thing as documented. This only works if it's just reporting a non-fatal error. In case of a fatal error, it can't be handled, so the translation is kind of pointless.

Re: Problems with C++ exceptions

#96
post #62

Earlier quoted context omitted.

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.

So you want to gradually reduce the fidelity of the error message as it makes its way up the stack.

That means that the top level handler can at best log a vague message.

That in turn means you must log along the way where you have more precise information about the failure, or you risk not having enough information to fix issues.

And that in turn means you must have lots of redundant logging, since each point in the stack doesn't know whether the abstraction it's invoking has already logged or not, or encapsulation would be violated.

Re: Problems with C++ exceptions

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

This is error-prone boilerplate that obscures the code, obscures the logs and is a known antipattern (log and throw) - which you're implementing manually, by hand, in the hope you never make a mistake.

You shouldn't do manually what you can automate.

Boilerplate can make you feel productive and can give you warm fuzzies inside when you see lots of patterns that look familiar, but seeing the same patterns over and over again is actually a smell; it's the smell of a missing abstraction.

Re: Problems with C++ exceptions

#98
post #96

Earlier quoted context omitted.

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.

So you want to gradually reduce the fidelity of the error message as it makes its way up the stack. That means that the top level handler can at best log a vague message. That in turn means you must log along the way where you have more precise information about the failure, or you risk not having enough information to fix issues. And that in turn means you must have lots of redundant logging, since each point in the…

> That in turn means you must log along the way where you have more precise information about the failure

Yes, that's the idea. You split the information into a diagnostic with stuff you deal with now and data that the upper layer will handle. The intersection between the data in these two things should be empty.

> And that in turn means you must have lots of redundant logging, since each point in the stack doesn't know whether the abstraction it's invoking has already logged or not, or encapsulation would be violated.

No. You print a diagnostic, about what exactly THIS layer was trying to do, you don't speculate what the upper layer was trying to do and try to log that. Every layer knows the lower layer has already logged the primary error, because an error object exists, and it also knows that the upper layer will print a diagnostic about what was the intention, so it only prints exactly what the error was in this layer.

> doesn't know whether the abstraction it's invoking has already logged or not, or encapsulation would be violated.

It knows that the lower layer has logged all stuff that that layer considered to be important, and that none of the data that is available to this layer was logged, since that is the responsibility of the caller.

An example:

    Document rendering incomplete, skipped publishing step.  Thumbnail #39 missing.  Failed to fetch image: Connection refused. [Discarded malformed packet with SYN flag.  Invalid data in src/network/tcp.c:894 parse_tcp_packet_quirks_mode]
Depending on the log level, you wouldn't show the later diagnostics. If there is a debug flag set, you can also add the function/line information to every step, not just to the last. If you are outtputing to stuff like syslog, you would put each diagnostic on its own line.

Re: Problems with C++ exceptions

#99
post #97

Earlier quoted context omitted.

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

This is error-prone boilerplate that obscures the code, obscures the logs and is a known antipattern (log and throw) - which you're implementing manually, by hand, in the hope you never make a mistake. You shouldn't do manually what you can automate. Boilerplate can make you feel productive and can give you warm fuzzies inside when you see lots of patterns that look familiar, but seeing the same patterns over and ove…

No. If you want to have the same information without that approach, you need to implement nested levels of error information, ten layers deep, each layer having a diagnostic, a log level, and file, line information, and a reason. In other words, you are building your own custom stack trace object, with annotated diagnostics. In addition, you can't reason about this at the upper level anyway, or you are rebuilding your application stack at some other place. The only thing you can do is to unwrap that object and serialize it into a diagnostic, which you could have done with less code, less memory and less compute. In addition, you would need to allocate on a failure path, which sounds like a nightmare.

Also you can never have comments in the code, because what you would write into the comment is now in the diagnostic itself. That means you can also turn on DEBUG_LEVEL_XXL and get a nice description, what the program actually does and why.

> This is error-prone

Why is it error-prune. You receive an error of one type and need to convert it into an error of another type. You need to handle that or the compiler will bark.

> obscures the logs

How does it obscure the logs?

Re: Problems with C++ exceptions

#100

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.

I'm not very familiar with proper exception usage in C++. Would you mind expanding a bit on this comment and describing the misunderstanding?
Post reply on HN