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 обрабатывать исключения на разных уровнях специфичности:

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

challenge icon

Задание

Легко

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

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

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

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

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

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