Question Details

No question body available.

Tags

design c++ exceptions

Answers (6)

June 30, 2025 Score: 16 Rep: 12,369 Quality: Expert Completeness: 80%

You are going overboard. One of the main advantages of exceptions is skipping over all the functions between where the error occurs, and where it can be fixed. If you wrap every function in try ... catch ... then you lose that benefit.

The default action for a function that contains calls that can throw, is to ensure the strong (or basic if strong is impossible) exception guarantee, and do nothing with the exceptions. Let them flow through.

If you can do something meaningful with an exception, only then do you catch it. That can mean, at boundaries between a low-level module and a high-level module, you wrap (some!) low-level exceptions in a high-level wrapper where that grouping is helpful the higher level.

From your example: Session::Open, perhaps some errors indicate transient network issues, so you catch those and retry, whereas other errors indicate a configuration issue. Because the consumers of this code want to distinguish between failing to connect and other issues, exceptions that would pass through are wrapped in Session::OpenFailed.

When I need to throw an exception, what kind of exception do I throw?

One with information that callers need to handle the exception.

If your caller wants to distinguish e.g. "206 Partial Content", "400 Bad Request", and "401 Unauthorised", then you probably should include the http error code. If they are just going to exit gracefully, then you don't.

Between the point where I throw the exception and the point where I catch and handle it, when do I have to catch & rethrow it ?

Catch and rethrow? Nowhere. Catch, add in extra context, throw nested exception: places where that extra context is needed by the caller.

Batch file processing from a UI has often extended requirements for returning error information, and it is clear that you can solve this by using a custom exception: it would contain a vector of exceptions thrown by the files in the batch. So you may have 3 layers: a lowest layer which catches the exception from processing a single file, an intermediate layer which collects those exceptions and bundles them in a single exception, and an outer layer which catches the exception, and displays the results to the user, but this is far from "catching and rethrowing in every function", so long as your functions also follow the advice to have short, focused functions.

When I do, what kind of exception do I throw ?

One with the extra context. See above.

This context isn't the what() of std::exception, it the type, and that type's data members. But you don't need excruciating detail for it's own sake. You only need detail when the handling is using that detail to make decisions.

N.b. while it is true that std::exception doesn't have a stack trace, there is now std::basicstacktrace, which you can include in your own exception type.

June 30, 2025 Score: 8 Rep: 13,147 Quality: High Completeness: 60%

Personally, I'd say you're coming at this from completely the wrong direction.
Stop thinking about throwing Exceptions for a minute and consider what, if anything, you can do if you catch one!

If the answer is "nothing", then there's no point using Exceptions. Period.

The most important part of Structured Exception Handling is that last part - "Handling".
"Handling" an Exception means doing something useful about it so that, to whatever code called you, it's as if the Exception never happened.

void A() { 
   B(); 
} 

void B() { try { C(); } catch() { / ... handle the exception usefully / } }

The method A() does not see any Exceptions, no matter what happens in C() - and that's a well-handled Exception.

So, coming back to your three questions:

When I need to throw an exception, what kind of exception do I throw?

Whatever kind you can usefully catch and do something about.
If you can't do anything useful with an Exception, don't catch it and just let it propagate upwards and outwards to something that can.

Between the point where I throw the exception and the point where I catch and handle it, when do I have to catch & rethrow it?

Almost NEVER.
The whole point of SEH is that you put handlers (catches) where they can do something useful. Let the Exception infrastructure worry about where they are.
Also, in early versions of .Net, Exceptions were effectively thrown twice; once to locate the handler (catch) block and collect any finally blocks on the way and then again to unwind the call stack, executing each of the finally blocks on the way before passing control to the selected handler. That made throwing Exceptions [a lot] painfully slow.

Many of us have seen often-generated, "boiler-plate" code that catches and re-throws Exceptions in every method. Yuck! Such code (a) abuses Structured Exception Handling to the point that it might as well return error codes from each and every method and (b) it may well well run appallingly slowly.

When I do, what kind of exception do I throw?

If you're building a library that will be shipped to clients, you might want to catch Exceptions at the process boundary and rethrow a new Exception containing only selected details and the StackTrace at that point of entry, thereby "hiding" the internal implementation of your library. Within your library, though, just let Exceptions propagate by themselves.

July 1, 2025 Score: 3 Rep: 199 Quality: Medium Completeness: 100%

Caveat: I've not programmed in C++. But I've used many languages with exceptions and exception handlers.

TL;DR Exceptions and error types both defer decisions; they have different default behaviors

  • Imagine a user has clicked on a Load Parameters button and selected several files
  • Some code tries to extract information from the files, but at some point it detects that something is wrong with the syntax and throws an exception
  • That exception propagates and at some point you want to handle that error by returning to a pre-load state, showing the user a message telling them what the issue was so that they can fix the files and click on the button again to retry
  • How would you bring all the relevant information to the catch(), including in what part of what file the issue was?
  • How would you log what happened?
  • More generally, when you write code that can throw, how do you make sure the person calling that code has all the information they need within the exception to handle the error properly?

This particular example seems like an abuse of exceptions for control flow, in that the following bullet

  • Some code tries to extract information from the files, but at some point it detects that something is wrong with the syntax and throws an exception

is really about getting information to a point higher in the call stack to interact with the user:

  • That exception propagates and at some point you want to handle that error by returning to a pre-load state, showing the user a message telling them what the issue was so that they can fix the files and click on the button again to retry

The way I approach this design is similar to Filip's comment, reproduced here for posterity:

Treat errors in user input as normal and expected—and design your code and screens accordingly; this is not what exceptions are best used for. Don't throw stack traces at users. Once user input has been validated and sanitized, pass it to the inner "core" of the application, throwing exceptions "at the boundary" if things are still wrong.

So here we might have a set of parsing modules responsible for extracting information from the user. In a language with sum types like Rust's enum, the main parser entrypoint could return a ParseResult | ParseError1. The result type provides necessary data, while the error type contains syntax error information. (If either type has multiple variants, they might further be split out!)

The caller can then handle each case appropriately, including passing (possibly transformed) errors back up the stack for formatting & display to the user.

The key ingredient here is that normal data influences normal control flow. Nothing about the user providing non-conforming inputs is exceptional for this application, so treat it as such.

This does mean that using exceptions as a kind of goto is largely ruled out: in most cases, the relevant tradeoffs of having to analyze goto-like behavior aren't worth it. If and when your case demands making those tradeoffs, then do so.

How do we actually use exceptions, then? When the case at hand is exceptional, and normal behavior is to crash the program to stop further execution. This doesn't mean we can't handle the exception—a caller might know better. But in the absence of such context, exceptions are the mechanism that says "defer the decision to handle this failure for later, defaulting to a crash."

Error-typed return values are the mechanism that says "defer the decision to handle this failure for later, defaulting to handling it."

Notes

  1. I'm not sure what the C++ equivalent is, but perhaps a union or different objects that respond to the same messages (trying to avoid "instance tests" here)—frankly object hierarchies have never made total sense for this particular kind of problem to me, though. This is where languages with rich type systems and pattern matching tend to shine, but I've seen similar techniques in Git's source code, for example.
July 2, 2025 Score: 2 Rep: 223 Quality: Low Completeness: 50%

Think about the person who has to handle the exception.

I find it useful to think of exceptions in the context of a contract: if I write a function, I promise to provide an output that satisfies certain postconditions in exchange for you providing inputs that satisfy certain preconditions. If the caller, or the author of the function (who may or may not be the same person) fails to honour the contract, an exception should be thrown.

Now, imagine that foo(...), bar(...), and baz(...) are functions that can throw exceptions that need to be handled: who (and who can be a user or another piece of code) can handle each exception? If the exceptions from foo(...) and bar(...) can be handled effectively in the same place, and in much the same way, but baz(...) cannot, you have discovered two separate classes of exception.

Many exceptions can only be handled by an appeal to a human: the file, whose name you supplied, doesn't exist, or your Internet is full. Again you can structure your exception classes so each exception corresponds to one remediation.

(This isn't exactly the same as Meyer's Design by Contract, but I suggest reading something about it.)

June 30, 2025 Score: -1 Rep: 4,657 Quality: Low Completeness: 50%

Caveat: My experience is primarily with Exceptions in JVM languages, I think the concepts are transferable but there may be implementation details I am unaware of.

I agree with the first concept (focussing on how exceptions are handled) in Phil W's answer, but disagree with how that gets flushed out.

Between the point where I throw the exception and the point where I catch and handle it, when do I have to catch & rethrow it ?

The case where I would consider try, catch and throw is where I only wanted to partially handle the exception / some cleanup not automatically handled by another mechanism.

In most other cases, you are adding a try/catch because there is a particular goal (or goals) you are trying to achieve:

  • Log and/or record statistics that a problem occurred.
  • Retry an operation for example try (again) to open a network connection.
    • There may be a timeout or counter to prevent infinite retries.
  • Translate and/or route a meaningful message/popup to the user so they know a problem occurred.
  • Prevent propagation of the exception into a thread pool i.e. keep a thread alive and return it to the pool.
  • Translate the error into another form:
    • HTTP Status code / message body for a REST request.
    • Regular status/error code.
    • A different exception - typically a higher level exception, for example "Document could not be opened"

When you throw a different exception (the final case above) the critical observation is that the new exception will also be need to be handled and you have the same choices for handling it, as you did for the original exception.

Exceptions carry information in three ways:

  • The type / type hierarchy of the exception.
  • Any fields that the exception contains (including causing/nested exceptions)
  • The location that the exception was thrown from, some exceptions contain a call stack however part of that stack can be implied from the location where the try/catch is located.

Interrogating the exception to find the location the exceptions was thrown from is generally consider bad practice, as most programmers do not expect exception handlers to do that / won't consider it when refactoring.

Therefore higher level exception handlers have less information than low level exception handlers - taken to the extreme a try/catch in the main function simply knows that an exception occurred "somewhere" in the code.

Therefore when you morph (throw a higher level exception) you are updating the way the exception contains it's information (it's no longer possible to identify the exact location the exception came from), so if higher level logic requires this, it will have to rely on the type of the exception or any fields that you choose to include.

When I need to throw an exception, what kind of exception do I throw?

This thought process also applies to the first time you throw an exception, say you choose to throw a FileNotFoundException, that may be acceptable if you know the higher level logic will only have to deal with one case where a file is loaded. If more than one file is loaded, additional context may be needed either when the first exception is thrown or as part of a higher level mapping to a different exception.

July 1, 2025 Score: -2 Rep: 1 Quality: Low Completeness: 20%

Example: Loading Multiple Files Let’s say a user clicks “Load Settings” and selects 5 files. One of them has an error. Here’s what should happen:

The code checks each file.

When it finds a mistake, it throws an exception that says:

“Error in file config3.txt at line 12: Unexpected word.”

That error goes up to the part of the program that deals with the Load button.

There, it shows the user a helpful message and lets them fix the file and try again.