Problems with C++ exceptions

Exception models across languages

  • Several comments contrast C++ with Java, Rust, Swift, Go, Python, and Elixir.
  • Swift’s throws is praised as “error as return path”: func f() throws -> T is conceptually T | Error, with mandatory handling and explicit try at call sites.
  • Swift 6’s typed throws are noted but seen as limited and sometimes impractical; some consider them not worth the complexity.
  • Java’s checked exceptions are criticized: they interact poorly with interfaces, generics, and FP/lambdas, and lead to “exception tunneling” wrappers.
  • Rust’s Result<T,E> and panics are discussed; Result is liked, but panics/unwinding and OOM handling are contentious.
  • Python’s exception behavior is viewed as similar to C++ (unchecked, can catch base type); discomfort mainly comes from C programmers who want exhaustiveness.
  • Elixir-style {:ok, v} | {:error, e} + supervision trees is mentioned as an alternative to pervasive exceptions.

Typed vs untyped / checked vs unchecked

  • Some argue exceptions should be part of a function’s contract (like Result<T,E> or Java checked exceptions).
  • Others argue typed/checked exceptions leak implementation details and make APIs brittle: changing internals (e.g., adding caching) can require breaking changes to exception signatures.
  • One view: in high-level code, you mostly either propagate or generically handle errors, so precise exception typing adds cost with little value.
  • Another view: not knowing all possible failure modes feels unsafe and makes some developers uncomfortable.

C++ RAII, exceptions, and resource management

  • The blog’s critique centers on a File_handle RAII example and “RAISI” (resource acquisition is separate from initialization).
  • Multiple commenters claim the article misunderstands idiomatic C++: exceptions should usually be caught far from where they’re thrown, with RAII cleaning up automatically on unwind.
  • Local try/catch around fopen is seen as the wrong pattern; better patterns include:
    • A RAII wrapper that stores errno or an error code;
    • Returning std::expected<T, std::error_code> or using std::error_code/std::exception hierarchies;
    • scope_guard/cleanup/defer-style helpers when a one-off wrapper is overkill.

Abstraction, contracts, and where to catch

  • One camp: failure modes “pierce abstractions,” so trying to specify them all (checked/typed exceptions) breaks encapsulation and complicates evolution. Exceptions should be caught only near the source or at top-level boundaries.
  • Another camp: allowing unknown exceptions and non-exhaustive handling is unsatisfying; they prefer explicit error codes or Result-style returns for clarity and exhaustiveness.

Debugging and logging

  • Lack of built-in stack traces for C++ exceptions is cited as a real pain point; it encourages broad try/catch and ad‑hoc logging.
  • Others argue that logging on every layer (“log and throw”) is boilerplate and an antipattern; they prefer stack traces plus selective context logging.
  • Chained/nested errors are proposed as a compromise, carrying both low‑level and high‑level context.

Complexity and evolution of C++

  • Several participants note that modern C++ (post‑11) is complex enough that many programmers misuse exceptions, contributing to the kind of code the article critiques.
  • There is mention of ongoing proposals for exception sets and noexcept regions, but also skepticism that C++’s exception story can be cleanly “fixed” at this point.