Обработка исключений в ООП
Часть раздела Объектно-ориентированное программирование путешествия по 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 и обработка исключений так хорошо работают вместе.
Задание
ЛегкоДавайте создадим безопасный калькулятор деления, который демонстрирует обработку исключений в объектно-ориентированном контексте. Вы создадите класс 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: Считайте три входных значения (каждое на отдельной строке):- Тип операции: либо
"divide", либо"sqrt" - Первое число (double)
- Второе число (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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер