Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 81 из 104.

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

Основной механизм использует три ключевых слова: try, throw и catch. Код, который может завершиться ошибкой, помещается в блок try, ошибки сигнализируются с помощью throw, а блоки catch обрабатывают конкретные типы исключений:

#include <iostream>
#include <stdexcept>

class BankAccount {
    double balance;
public:
    BankAccount(double b) : balance(b) {}
    
    void withdraw(double amount) {
        if (amount > balance) {
            throw std::runtime_error("Insufficient funds");
        }
        balance -= amount;
    }
    
    double getBalance() const { return balance; }
};

int main() {
    BankAccount account(100.0);
    
    try {
        account.withdraw(150.0);
    } catch (const std::runtime_error& e) {
        std::cout << "Error: " << e.what() << "\n";
    }
    
    std::cout << "Balance: " << account.getBalance() << "\n";
}

Стандартная библиотека C++ предоставляет иерархию классов исключений, базовым для которых является std::exception. К распространенным производным классам относятся std::runtime_error для проблем времени выполнения и std::logic_error для ошибок в логике программирования. Все стандартные исключения предоставляют метод what(), который возвращает сообщение об ошибке.

Когда генерируется исключение, происходит развертывание стека — вызываются деструкторы для всех локальных объектов, пока программа ищет подходящий блок catch. Это обеспечивает корректную очистку ресурсов даже при возникновении ошибок, именно поэтому RAII и обработка исключений так хорошо работают вместе.

challenge icon

Задание

Легко

Давайте создадим безопасный калькулятор деления, который демонстрирует обработку исключений в объектно-ориентированном контексте. Вы создадите класс Calculator, который выполняет операции деления и выбрасывает информативные исключения, когда что-то идет не так, а затем изящно обрабатывает эти исключения в вашей основной программе.

Вы организуете свой код в трех файлах:

  • Calculator.h: Определите класс Calculator, который выполняет математические операции с надлежащей обработкой ошибок.

    Ваш Calculator должен иметь метод divide(double numerator, double denominator), который возвращает результат деления. Однако, если знаменатель равен нулю, он должен выбрасывать std::runtime_error с сообщением "Division by zero". Если числитель или знаменатель отрицательны, он должен выбрасывать std::invalid_argument с сообщением "Negative values not allowed".

    Также включите метод safeSqrt(double value), который возвращает квадратный корень из значения. Если значение отрицательное, выбросьте std::runtime_error с сообщением "Cannot compute square root of negative number". Вам нужно будет включить <cmath> для функции sqrt.

  • Calculator.cpp: Реализуйте методы, объявленные в заголовочном файле. Обязательно включите необходимые заголовки (<stdexcept> и <cmath>) и проверяйте условия перед выполнением операций.
  • main.cpp: Считайте три входных значения (каждое на отдельной строке):
    1. Тип операции: либо "divide", либо "sqrt"
    2. Первое число (double)
    3. Второе число (double) — используется только для деления, игнорируется для sqrt

    Создайте объект Calculator и выполните запрошенную операцию внутри блока try. Если операция прошла успешно, выведите "Result: [value]". Если поймано исключение, выведите "Error: [message]", где сообщение берется из метода исключения what().

    После блока try-catch (независимо от того, возникло исключение или нет), выведите "Calculation complete", чтобы продемонстрировать, что программа продолжает выполнение после обработки исключений.

Например, при входных данных "divide", 10 и 2:

Result: 5
Calculation complete

При входных данных "divide", 10 и 0:

Error: Division by zero
Calculation complete

При входных данных "divide", -5 и 2:

Error: Negative values not allowed
Calculation complete

При входных данных "sqrt", 16 и 0:

Result: 4
Calculation complete

При входных данных "sqrt", -9 и 0:

Error: Cannot compute square root of negative number
Calculation complete

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

Шпаргалка

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

try {
    // Код, который может выбросить исключение
    account.withdraw(150.0);
} catch (const std::runtime_error& e) {
    std::cout << "Error: " << e.what() << "\n";
}

Выброс исключений из методов класса:

void withdraw(double amount) {
    if (amount > balance) {
        throw std::runtime_error("Insufficient funds");
    }
    balance -= amount;
}

Стандартная библиотека C++ предоставляет классы исключений, производные от std::exception. Общие типы включают:

  • std::runtime_error — для проблем времени выполнения
  • std::logic_error — для логических ошибок программирования
  • std::invalid_argument — для недопустимых аргументов

Все стандартные исключения предоставляют метод what(), который возвращает сообщение об ошибке.

При возникновении исключения происходит развертывание стека — вызываются деструкторы для всех локальных объектов, пока программа ищет подходящий блок catch, что обеспечивает правильную очистку ресурсов.

Подключите <stdexcept>, чтобы использовать стандартные классы исключений.

Попробуйте сами

#include <iostream>
#include <string>
#include "Calculator.h"

using namespace std;

int main() {
    // Чтение входных данных
    string operation;
    double num1, num2;
    
    cin >> operation;
    cin >> num1;
    cin >> num2;
    
    // Создание объекта Calculator
    Calculator calc;
    
    // TODO: Используйте блок try-catch для выполнения операции
    // - Если operation равна "divide", вызовите calc.divide(num1, num2)
    // - Если operation равна "sqrt", вызовите calc.safeSqrt(num1)
    // - В случае успеха выведите "Result: ", а затем результат
    // - Перехватите исключения и выведите "Error: ", а затем сообщение об исключении (используйте what())
    // - После try-catch всегда выводите "Calculation complete"
    
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование