Skip to main content

Custom Exception Types

intermediate20 min readLesson 118 of 204

Deriving from std::runtime_error, carrying structured data, and designing the type around the handler's decision.

When what() strings are not enough structure, define exception types. Derive from std::runtime_error (or logic_error) and carry the data the handler needs:

#include <stdexcept>
#include <string>

class ValidationError : public std::runtime_error {
public:
    ValidationError(std::string field, std::string message)
        : std::runtime_error{field + ": " + message},
          field_{std::move(field)}, message_{std::move(message)} {}

    const std::string& field() const noexcept { return field_; }

private:
    std::string field_;
    std::string message_;
};

Rules that keep custom exceptions safe:

  • constructor must not throw (it would abort during unwinding);
  • what() is satisfied for free by std::runtime_error;
  • copy members exist by default — exceptions are copied as they unwind, so members must be copyable;
  • catch handlers get const ValidationError& and can read .field() without parsing strings.

Design the type around the handler's decision: what would a caller do differently for this failure than for any other? If the answer is "nothing", a plain std::runtime_error string was enough.