Пользовательские иерархии исключений
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 82 из 104.
Хотя стандартная библиотека предоставляет полезные классы исключений, такие как std::runtime_error, реальные приложения часто выигрывают от создания иерархий пользовательских исключений. Определяя собственные классы исключений, которые наследуются от стандартных исключений, вы можете создавать предметно-ориентированные типы ошибок, содержащие дополнительный контекст и позволяющие выполнять более точную обработку ошибок.
Пользовательский класс исключения обычно наследуется от std::exception или одного из его производных классов и переопределяет метод what():
#include <exception>
#include <string>
class DatabaseException : public std::exception {
std::string message;
public:
DatabaseException(const std::string& msg) : message(msg) {}
const char* what() const noexcept override { return message.c_str(); }
};
class ConnectionException : public DatabaseException {
public:
ConnectionException(const std::string& host)
: DatabaseException("Failed to connect to: " + host) {}
};
class QueryException : public DatabaseException {
public:
QueryException(const std::string& query)
: DatabaseException("Invalid query: " + query) {}
};Эта иерархия позволяет конструкциям catch blocks обрабатывать исключения с разными уровнями специфичности:
try {
// операции с базой данных...
throw ConnectionException("localhost");
} catch (const ConnectionException& e) {
std::cout << "Connection issue: " << e.what() << "\n";
} catch (const DatabaseException& e) {
std::cout << "Database error: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "General error: " << e.what() << "\n";
}Спецификатор noexcept у what() важен — он гарантирует, что метод не выбросит исключение, что требуется интерфейсом base class. Располагайте блоки catch от наиболее специфичных к наиболее общим, поскольку C++ использует первый подходящий обработчик.
Задание
ЛегкоДавайте создадим систему обработки файлов с иерархией пользовательских исключений, которая обрабатывает разные типы ошибок, связанных с файлами. Вы создадите предметно-ориентированные исключения, наследующиеся от базового класса исключений, что позволит точно обрабатывать ошибки на разных уровнях специфичности.
Вы организуете код в трёх файлах:
FileExceptions.h: определите свою иерархию пользовательских исключений для операций с файлами.Создайте базовый класс
FileException, который наследуется отstd::exception. Он должен хранить сообщение об ошибке и переопределять методwhat()с помощью спецификатораnoexcept.Затем создайте два производных класса исключений:
FileNotFoundException: принимает имя файла и создаёт сообщение:File not found: [filename]PermissionDeniedException: принимает имя файла и создаёт сообщение:Permission denied: [filename]
Каждый производный класс должен вызывать конструктор родительского класса с соответствующим сообщением.
FileProcessor.h: определите классFileProcessor, который имитирует операции с файлами и выбрасывает ваши пользовательские исключения.В вашем
FileProcessorдолжен быть методopenFile(const std::string& filename), который имитирует открытие файла:- Если имя файла содержит
missing, выбросьтеFileNotFoundException - Если имя файла содержит
locked, выбросьтеPermissionDeniedException - В противном случае верните строку
Successfully opened: [filename]
- Если имя файла содержит
main.cpp: считайте одно имя файла из входных данных.Создайте
FileProcessorи попытайтесь открыть файл. Используйте блок try-catch с обработчиками, упорядоченными от наиболее специфичного к наиболее общему:- Перехватите
FileNotFoundExceptionи выведите:File Error: [message] - Перехватите
PermissionDeniedExceptionи выведите:Access Error: [message] - Перехватите
FileExceptionи выведите:General File Error: [message]
Если исключение не выброшено, выведите сообщение об успешном выполнении, возвращённое методом
openFile().- Перехватите
Например, при входных данных report.txt:
Successfully opened: report.txtПри входных данных missing_data.csv:
File Error: File not found: missing_data.csvПри входных данных locked_config.ini:
Access Error: Permission denied: locked_config.iniЭто задание демонстрирует, как иерархии пользовательских исключений позволяют обрабатывать ошибки на подходящем уровне детализации: перехватывать конкретные исключения, когда нужна точная обработка, или перехватывать базовое исключение, когда требуется единообразно обрабатывать все ошибки файлов.
Попробуйте сами
#include <iostream>
#include <string>
#include "FileExceptions.h"
#include "FileProcessor.h"
using namespace std;
int main() {
string filename;
cin >> filename;
FileProcessor processor;
// TODO: Используйте блок try-catch для обработки исключений
// Упорядочьте обработчики от наиболее специфичных к наиболее общим:
// 1. Перехватите FileNotFoundException - выведите: "File Error: [message]"
// 2. Перехватите PermissionDeniedException - выведите: "Access Error: [message]"
// 3. Перехватите FileException - выведите: "General File Error: [message]"
// Если исключения нет, выведите сообщение об успехе из openFile()
try {
// TODO: Вызовите processor.openFile() и обработайте результат
}
// TODO: Добавьте блоки catch в порядке от наиболее специфичных к наиболее общим
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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++