Error Handling in C++: try/catch and Advanced Techniques

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Error Handling in C++: From try/catch to Modern Approaches

\n\n

The exception mechanism in C++ is a powerful tool that requires deep understanding. While basic use of try/catch is known to most developers, professional error handling involves much more: from exception safety guarantees to alternative strategies without exceptions. In this article, we will explore advanced error handling techniques in modern C++.

\n\n

1. Exception Safety Guarantees: The Foundation of Reliable Code

\n\n

Any function that can throw an exception must provide one of three safety guarantees:

\n\n
    \n
  • Basic Guarantee: in case of an exception, object invariants are preserved, and there are no resource leaks. The object may be in an unpredictable but valid state.
  • \n
  • Strong Guarantee: the operation either completes fully, or the program state remains unchanged (atomicity). Achieved through the copy-and-swap idiom.
  • \n
  • Noexcept Guarantee: the function never throws exceptions. Critical for destructors, move operations, and operations used in containers.
  • \n
\n\n
// Example of strong guarantee via copy-and-swap\nclass Vector {\n    int* data;\n    size_t sz, cap;\npublic:\n    void push_back(int value) {\n        if (sz == cap) {\n            // 1. Create a copy\n            Vector tmp(*this);\n            // 2. Modify the copy (may throw an exception)\n            tmp.reserve(cap * 2);\n            // 3. Swap (noexcept!)\n            swap(tmp);\n        }\n        data[sz++] = value;  // noexcept\n    }\n    \n    void swap(Vector& other) noexcept {\n        std::swap(data, other.data);\n        std::swap(sz, other.sz);\n        std::swap(cap, other.cap);\n    }\n};
\n\n

2. RAII as the Foundation of Resource Management

\n\n

The RAII (Resource Acquisition Is Initialization) idiom is not just about smart pointers. It is a fundamental principle that helps avoid resource leaks during exceptions:

\n\n
    \n
  • Resource is acquired in the constructor
  • \n
  • Released in the destructor (which is noexcept by default)
  • \n
  • An exception in a destructor is UB (undefined behavior)
  • \n
\n\n
// RAII wrapper for a file descriptor\nclass FileDescriptor {\n    int fd;\npublic:\n    explicit FileDescriptor(const char* path) \n        : fd(open(path, O_RDONLY)) {\n        if (fd == -1) throw std::runtime_error("Failed to open file");\n    }\n    \n    ~FileDescriptor() noexcept {\n        if (fd != -1) close(fd);\n    }\n    \n    // Prohibit copying, allow move\n    FileDescriptor(FileDescriptor&& other) noexcept : fd(other.fd) {\n        other.fd = -1;\n    }\n};
\n\n

3. Modern Alternatives to Exceptions: std::optional and std::expected

\n\n

Not all errors should be handled via exceptions. For expected errors (invalid input, missing data), it is better to use wrapper types:

\n\n
#include <optional>\n#include <expected>  // C++23\n\n// std::optional — for optional values\nstd::optional<int> safe_divide(int a, int b) {\n    if (b == 0) return std::nullopt;\n    return a / b;\n}\n\n// std::expected — for errors with information (C++23)\nstd::expected<double, std::string> sqrt_checked(double x) {\n    if (x < 0) return std::unexpected("Negative input");\n    return std::sqrt(x);\n}\n\n// Usage\nauto result = safe_divide(10, 0);\nif (result) {\n    std::cout << *result;\n} else {\n    std::cout << "Division by zero";\n}
\n\n

4. noexcept: Specification and Optimizations

\n\n

The noexcept specifier is not just documentation. It affects performance and

Blogs

Book Recommendations