---
description: C++ Error Handling
alwaysApply: false
---

# C++ Error Handling

Exceptions for exceptional conditions. `std::expected` for expected failures. RAII guarantees cleanup.

## Exception Safety Guarantees

Every function provides one — document which:

- **No-throw** — never throws, mark `noexcept` (destructors, move ops, swap)
- **Strong** — if exception thrown, state rolls back (copy-and-swap idiom)
- **Basic** — invariants preserved, no leaks (minimum acceptable)

## noexcept Rules

- Always: destructors, move constructors/assignment, swap
- Conditional: `noexcept(noexcept(expr))` for templates
- Matters: `std::vector` copies instead of moves if move isn't `noexcept`

## std::expected (C++23)

```cpp
auto validate(const Request& req)
    -> std::expected<ValidatedUser, ValidationError> {
    if (req.name.empty()) return std::unexpected{ValidationError::empty_name};
    return ValidatedUser{req.name, req.email};
}

// Monadic chaining
auto result = validate(request)
    .and_then([&](auto v) { return save(db, v); })
    .transform([](auto s) { return Response::from(s); });
```

## Exceptions

```cpp
// Throw by value, catch by const reference, specific first
try {
    auto user = find_user(id);
} catch (const NotFoundError& e) {
    return http::response(404, e.what());
} catch (const std::exception& e) {
    log::error("Unexpected: {}", e.what());
    return http::response(500, "Internal error");
}
```

## Assertions

```cpp
assert(!data.empty() && "process() called with empty data");
static_assert(sizeof(Packet) == 64, "Packet must be 64 bytes");
static_assert(std::is_nothrow_move_constructible_v<Buffer>);
```

## Anti-Patterns

- **catch(...) without rethrowing** — at minimum: `catch (...) { log(); throw; }`
- **Throwing in destructors** — `std::terminate` will be called; log and continue
- **Exceptions across DLL boundaries** — use error codes or `std::expected` at library boundaries
