Menu
Coddy logo textTech

Пользовательские иерархии исключений

Часть раздела Объектно-ориентированное программирование путешествия по 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++ использует первый подходящий обработчик.

challenge icon

Задание

Легко

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

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

  • 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 с обработчиками, упорядоченными от наиболее специфичного к наиболее общему:

    1. Перехватите FileNotFoundException и выведите: File Error: [message]
    2. Перехватите PermissionDeniedException и выведите: Access Error: [message]
    3. Перехватите 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;
}
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор C++