Обработка исключений

В процессе работы программы могут случаться различные непредвиденные проблемы.
Например:
- При скачивании файла оборвалось соединение...
-- Такое вполне может произойти, например если у пользователя плохой сигнал wi-fi.
- При попытке записать файл на USB устройство и устройство оказалось недоступно...
-- Такое может случиться, если пользователь задел рукой USB устройство.
- При попытке выполнить запрос к базе данных, может оказаться, что она недоступна.
-- Такое может случиться, если сервер с БД выключился/перезагрузился и т.п.
Примеров можно приводить много. Любая из этих проблем, потенциально может вывести из строя вашу программу.

Ошибки в работе программы - это конечно плохо, и к сожалению они случаются. Было бы неплохо их максимально предсказывать и если они случаются, то лучше постараться быть вкурсе и держать ситуацию под контролем.

Хорошие новости

В c++ есть механизм обработки ошибок исключений!

Исключения в C++ предоставляют механизм для обработки ошибок и нештатных ситуаций, который отделяет код обработки ошибок от основного потока выполнения программы.

Основные компоненты обработки исключений
  1. try - блок кода, в котором могут возникнуть исключения

  2. catch - блоки обработки исключений

  3. throw - оператор генерации исключения


Базовый пример обработки исключений

#include <iostream> #include <stdexcept> double divide(int a, int b) { if (b == 0) { throw std::runtime_error("Division by zero!"); } return static_cast<double>(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 occurred!" << std::endl; } return 0; }
Пояснение:
  • Функция divide() при попытке деления на 0 генерирует исключение типа std::runtime_error

  • Блок try содержит код, который может генерировать исключения

  • Блок catch ловит конкретное исключение и обрабатывает его

  • catch (...) ловит любые исключения, не пойманные предыдущими обработчиками

  • Метод what() возвращает сообщение об ошибке


Иерархия стандартных исключений

std::exception
├── std::logic_error
│   ├── std::invalid_argument
│   ├── std::domain_error
│   ├── std::length_error
│   └── std::out_of_range
├── std::runtime_error
│   ├── std::range_error
│   ├── std::overflow_error
│   └── std::underflow_error
└── std::bad_alloc (для ошибок выделения памяти)
Пример использования разных типов исключений:
#include <iostream> #include <stdexcept> #include <vector> #include <limits> void processInput(int value) { if (value < 0) { throw std::invalid_argument("Negative value not allowed"); } if (value > 100) { throw std::out_of_range("Value exceeds maximum limit"); } if (value > std::numeric_limits<int>::max() - 1000) { throw std::overflow_error("Potential overflow detected"); } std::vector<int> vec; vec.reserve(value); // Может вызвать std::bad_alloc при очень больших значениях std::cout << "Processing value: " << value << std::endl; } int main() { try { processInput(-5); } catch (const std::invalid_argument& e) { std::cerr << "Invalid argument: " << e.what() << std::endl; } catch (const std::out_of_range& e) { std::cerr << "Out of range: " << e.what() << std::endl; } catch (const std::overflow_error& e) { std::cerr << "Overflow: " << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << "Standard exception: " << e.what() << std::endl; } catch (...) { std::cerr << "Unknown exception occurred" << std::endl; } return 0; }
Пояснение:
  • Разные типы исключений используются для разных категорий ошибок

  • Обработчики расположены от наиболее специфичных к наиболее общим

  • Все стандартные исключения наследуются от std::exception


Распространение исключений по стеку вызовов

Исключения могут распространяться вверх по стеку вызовов:
#include <iostream> #include <stdexcept> void innerFunction() { throw std::runtime_error("Error from inner function"); } void middleFunction() { innerFunction(); } void outerFunction() { try { middleFunction(); } catch (const std::runtime_error& e) { std::cerr << "Caught in outerFunction: " << e.what() << std::endl; // Можно либо обработать, либо передать дальше throw; // Повторная генерация того же исключения } } int main() { try { outerFunction(); } catch (const std::exception& e) { std::cerr << "Caught in main: " << e.what() << std::endl; } return 0; }
Пояснение:
  • Исключение из innerFunction() проходит через middleFunction()

  • outerFunction() ловит исключение, добавляет информацию и передает дальше

  • main() получает окончательно обработанное исключение


Гарантии безопасности исключений

C++ определяет три уровня гарантий безопасности исключений:

  1. No-throw guarantee - функция никогда не генерирует исключений

  2. Strong exception safety - при возникновении исключения состояние программы остается таким, как было до вызова функции

  3. Basic exception safety - при возникновении исключения сохраняется валидность состояния, но возможна потеря данных

Пример с разными гарантиями:
#include <iostream> #include <stdexcept> #include <vector> // No-throw guarantee (помечено noexcept) void printSize(const std::vector<int>& vec) noexcept { std::cout << "Size: " << vec.size() << std::endl; } // Strong exception safety void addToVector(std::vector<int>& vec, int value) { std::vector<int> temp = vec; // Копируем temp.push_back(value); // Модифицируем копию vec.swap(temp); // Обмениваем только если все успешно } // Basic exception safety void unsafeAddToVector(std::vector<int>& vec, int value) { vec.push_back(value); // Может вызвать std::bad_alloc } int main() { std::vector<int> numbers = {1, 2, 3}; printSize(numbers); // Безопасно try { addToVector(numbers, 4); // Сильная гарантия unsafeAddToVector(numbers, 5); // Базовая гарантия } catch (const std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } printSize(numbers); return 0; }
Пояснение:
  • noexcept указывает, что функция не генерирует исключений

  • Реализация с сильной гарантией сначала работает с копией

  • Базовая гарантия означает, что объект останется в валидном состоянии


Исключения в конструкторах и деструкторах

  • В конструкторах исключения - основной способ сообщить об ошибке

  • Деструкторы по умолчанию noexcept (не должны генерировать исключения)

#include <iostream> #include <stdexcept> class Resource { public: Resource(int id) : id(id) { if (id < 0) { throw std::invalid_argument("Invalid resource ID"); } std::cout << "Resource " << id << " created\n"; } ~Resource() noexcept { std::cout << "Resource " << id << " destroyed\n"; } private: int id; }; int main() { try { Resource r1(1); Resource r2(-1); // Вызовет исключение Resource r3(2); // Не будет создан } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; } return 0; }
Пояснение:
  • При исключении в конструкторе деструктор не вызывается

  • Ресурсы, выделенные до исключения, нужно освобождать

  • Деструкторы должны быть noexcept для корректной работы при раскрутке стека


Обработка исключений в C++ предоставляет мощный механизм для:

  1. Разделения нормального потока выполнения и обработки ошибок

  2. Гибкой обработки ошибок на разных уровнях программы

  3. Создания надежного и поддерживаемого кода


Комментарии

Добавить комментарий

Чтобы оставить комменатрий необходимо Авторизоваться