Пользовательские иерархии исключений
Часть раздела Объектно-ориентированное программирование путешествия по 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 обрабатывать исключения на разных уровнях специфичности:
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() важен — он гарантирует, что метод не будет генерировать исключения, что требуется интерфейсом базового класса. Располагайте блоки catch от наиболее специфичных к наиболее общим, так как C++ использует первый подходящий обработчик.
Задание
ЛегкоДавайте создадим систему обработки файлов с иерархией пользовательских исключений, которая обрабатывает различные типы ошибок, связанных с файлами. Вы создадите специфичные для предметной области исключения, наследуемые от базового класса исключений, что позволит точно обрабатывать ошибки на разных уровнях детализации.
Вы организуете свой код в трех файлах:
FileExceptions.h: Определите свою иерархию пользовательских исключений для операций с файлами.Создайте базовый класс
FileException, который наследуется отstd::exception. Он должен хранить сообщение об ошибке и переопределять методwhat()со спецификаторомnoexcept.Затем создайте два производных класса исключений:
FileNotFoundException— принимает имя файла и создает сообщение:"File not found: "+ имя файлаPermissionDeniedException— принимает имя файла и создает сообщение:"Permission denied: "+ имя файла
Каждый производный класс должен вызывать конструктор родителя с соответствующим сообщением.
FileProcessor.h: Определите классFileProcessor, который имитирует операции с файлами и выбрасывает ваши пользовательские исключения.Ваш
FileProcessorдолжен иметь методopenFile(const std::string& filename), который имитирует открытие файла:- Если имя файла содержит
"missing", выбросьтеFileNotFoundException - Если имя файла содержит
"locked", выбросьтеPermissionDeniedException - В противном случае верните строку
"Successfully opened: "+ имя файла
- Если имя файла содержит
main.cpp: Считайте одно имя файла из входных данных.Создайте
FileProcessorи попытайтесь открыть файл. Используйте блок try-catch с обработчиками, упорядоченными от самых специфичных к самым общим:- Перехватите
FileNotFoundExceptionи выведите:"File Error: "+e.what() - Перехватите
PermissionDeniedExceptionи выведите:"Access Error: "+e.what() - Перехватите
FileExceptionи выведите:"General File Error: "+e.what()
Если исключение не выброшено, выведите сообщение об успехе, возвращенное методом
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Эта задача демонстрирует, как иерархии пользовательских исключений позволяют обрабатывать ошибки на соответствующем уровне детализации — перехватывать конкретные исключения, когда вам нужна точная обработка, или перехватывать базовое исключение, когда вы хотите единообразно обрабатывать все ошибки файлов.
Шпаргалка
Пользовательские классы исключений наследуются от 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) {}
};Обрабатывайте исключения на разных уровнях специфичности, располагая блоки catch от самых специфичных к самым общим:
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() гарантирует, что метод не будет генерировать исключения, что требуется интерфейсом базового класса.
Попробуйте сами
#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Итоги — Обобщенный контейнер