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

Глава 5. Продвинутые концепции #

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

1. Базовые принципы обработки исключений #

Обработка исключений — это механизм в C++, позволяющий эффективно управлять ошибками и непредвиденными ситуациями во время выполнения программы. Основная цель механизма исключений — разделить код обработки ошибок и основную логику программы, повысив читаемость и надежность кода.

Ключевые концепции обработки исключений:

  • Выявление исключительных ситуаций во время выполнения программы
  • Передача информации об ошибке между различными частями программы
  • Безопасный выход из аварийных состояний
  • Предотвращение аварийного завершения программы

Пример базового использования: #

#include <iostream>
#include <stdexcept>

double divide(double a, double b) {
    if (b == 0) {
        throw std::runtime_error("Деление на ноль невозможно!");
    }
    return a / b;
}

int main() {
    try {
        double result = divide(10, 0);
        std::cout << result << std::endl;
    }
    catch (const std::runtime_error& e) {
        std::cerr << "Ошибка: " << e.what() << std::endl;
    }
    return 0;
}

2. Типы исключений #

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

  1. std::exception — базовый класс для всех стандартных исключений

  2. std::logic_error — ошибки, которые теоретически могут быть обнаружены до выполнения программы

    • std::invalid_argument
    • std::domain_error
    • std::length_error
  3. std::runtime_error — ошибки, которые могут быть обнаружены только во время выполнения

    • std::overflow_error
    • std::underflow_error
    • std::range_error

Пример использования различных типов исключений: #

#include <stdexcept>
#include <vector>

void processVector(const std::vector<int>& vec) {
    if (vec.empty()) {
        throw std::length_error("Вектор пуст");
    }
    if (vec.size() > 1000) {
        throw std::range_error("Слишком большой размер вектора");
    }
    // Логика обработки вектора
}

3. Блоки try-catch #

Блоки try-catch позволяют перехватывать и обрабатывать исключения.

Синтаксис:

try {
    // Код, который может сгенерировать исключение
} 
catch (тип_исключения1 параметр1) {
    // Обработка первого типа исключения
} 
catch (тип_исключения2 параметр2) {
    // Обработка второго типа исключения
}
catch (...) {
    // Обработка всех остальных исключений
}

Пример многоуровневой обработки: #

#include <iostream>
#include <stdexcept>
#include <string>

void performOperation(int value) {
    if (value < 0) {
        throw std::invalid_argument("Отрицательное значение");
    }
    if (value > 1000) {
        throw std::runtime_error("Слишком большое значение");
    }
}

int main() {
    try {
        performOperation(-5);
    }
    catch (const std::invalid_argument& e) {
        std::cerr << "Ошибка ввода: " << e.what() << std::endl;
    }
    catch (const std::runtime_error& e) {
        std::cerr << "Ошибка выполнения: " << e.what() << std::endl;
    }
    catch (...) {
        std::cerr << "Неизвестная ошибка" << std::endl;
    }
    return 0;
}

4. Создание собственных исключений #

Можно создавать пользовательские классы исключений, наследуя их от стандартных или базового класса std::exception.

#include <stdexcept>
#include <string>

class DatabaseException : public std::runtime_error {
public:
    DatabaseException(const std::string& message)
        : std::runtime_error(message) {}
};

class ConnectionException : public DatabaseException {
public:
    ConnectionException(const std::string& message)
        : DatabaseException("Ошибка подключения: " + message) {}
};

void connectToDatabase() {
    bool connectionFailed = true;
    if (connectionFailed) {
        throw ConnectionException("Сервер не отвечает");
    }
}

5. Раскрутка стека (Stack Unwinding) #

Раскрутка стека — процесс автоматического освобождения ресурсов и вызова деструкторов при распространении исключения вверх по стеку вызовов.

#include <iostream>
#include <memory>

class ResourceManager {
public:
    ResourceManager() { std::cout << "Ресурс создан\n"; }
    ~ResourceManager() { std::cout << "Ресурс освобождён\n"; }
};

void functionC() {
    ResourceManager res;
    throw std::runtime_error("Ошибка в функции C");
}

void functionB() {
    ResourceManager res;
    functionC();
}

void functionA() {
    ResourceManager res;
    try {
        functionB();
    }
    catch (const std::exception& e) {
        std::cerr << "Перехвачено исключение: " << e.what() << std::endl;
    }
}

int main() {
    functionA();
    return 0;
}

Ключевые принципы раскрутки стека: #

  • Автоматический вызов деструкторов для локальных объектов
  • Освобождение захваченных ресурсов
  • Поиск подходящего обработчика исключения

Рекомендации по использованию исключений #

  1. Используйте исключения для обработки действительно исключительных ситуаций
  2. Не используйте исключения для контроля штатного выполнения программы
  3. Перехватывайте конкретные типы исключений
  4. Используйте умные указатели и RAII для безопасного управления ресурсами
  5. Документируйте возможные исключения в функциях

Заключение #

Обработка исключений — мощный механизм управления ошибками в C++. Правильное использование исключений делает код более надёжным, читаемым и облегчает диагностику проблем во время выполнения программы.