Error Handling in C++: try/catch and Beyond — A Complete Guide

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

Introduction to Error Handling in C++

Error handling is one of the key tasks in developing reliable software in C++. Unlike some languages (e.g., Python or Java), where exceptions are the only standard mechanism, C++ provides the developer with several approaches. The stability, performance, and readability of the code depend on choosing the right strategy. In this article, we will analyze the classic try/catch construct, modern features of the C++17/20 standard, as well as alternative error handling methods.



1. Classic approach: try/catch construct

The main exception handling mechanism in C++ is built on three key words: try, catch, and throw. An exception is an object of any type that is "thrown" when an error occurs and "caught" in the corresponding block.



#include <iostream>#include <stdexcept>

double divide(double a, double b) { if (b == 0) { throw std::runtime_error("Division by zero!"); } return a / b;}

int main() { try { double result = divide(10, 0); std::cout << "Result: " << result << std::endl; } catch (const std::runtime_error& e) { std::cerr << "Error: " << e.what() << std::endl; } catch (...) { std::cerr << "Unknown error!" << std::endl; } return 0;}


Important points:

  • Exception hierarchy: it is recommended to throw objects of classes inherited from std::exception (e.g., std::runtime_error, std::logic_error).
  • Catching by reference: always catch exceptions by const reference (const std::exception&) to avoid slicing and unnecessary copying.
  • Universal handler: catch (...) catches any exceptions but does not provide access to the error object. Use it only as a last resort.


2. Alternative approaches: return codes and std::optional

Exceptions are not always the best choice. In high-load systems or embedded programming, lighter mechanisms are often preferred.



2.1. Return codes (error codes)

The function returns an integer or enumeration indicating success or the type of error. This method is simple and efficient but requires explicit checking of the result.



enum class ErrorCode { Success, DivisionByZero, Overflow };

ErrorCode safe_divide(double a, double b, double& result) { if (b == 0.0) return ErrorCode::DivisionByZero; result = a / b; return ErrorCode::Success;}

int main() { double res; ErrorCode err = safe_divide(10, 0, res); if (err != ErrorCode::Success) { // Error handling }}


2.2. std::optional (C++17)

A container that can either contain a value or be empty. It is ideal for situations where the error is the absence of a result.



#include <optional>

std::optional<double> divide_opt(double a, double b) { if (b == 0.0) return std::nullopt; return a / b;}

int main() { auto result = divide_opt(10, 2); if (result) { std::cout << *result << std::endl; } else { std::cout << "Division error" << std::endl; }}


3. Modern features: noexcept and std::expected (C++23)

The C++ standard continues to evolve, offering increasingly expressive tools for working with errors.



3.1. The noexcept specifier

The noexcept keyword indicates that a function does not throw exceptions. This helps the compiler optimize the code and makes the interface clearer.



void l                

Blogs

Book Recommendations