Создание своих типов исключений

Последнее обновление: 18.03.2023

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

#include <iostream>

class AgeException
{
public: 
    AgeException(std::string message): message{message}{}
    std::string getMessage() const {return message;}
private:
    std::string message;
};

class Person
{
public:
    Person(std::string name, unsigned age)
    {
        if(!age||age>110)   // если возраст равен 0 или больше 110
        {
            throw AgeException{"Invalid age"};
        }
        this->name = name;
        this->age = age;
    }
    void print() const
    {
        std::cout << "Name: " << name << "\tAge: " << age << std::endl;
    }
private:
    std::string name;
    unsigned age;
};

int main()
{
    try
    {
        Person tom{"Tom", 38};      // Корректные данные
        tom.print();

        Person bob{"Bob", 1500};    // Некорректные данные
        bob.print();
    }
    catch (const AgeException& ex)
    {
        std::cout << ex.getMessage() << std::endl;
    }
}

Здесь определяется класс Person, в конструктор которого передается имя и возраст пользователя. Однако нам необходимо, чтобы возраст был в некотором разумном диапазоне, например, от 1 до 110. И в этом случае в конструкторе класса проверяем переданное значение возраста. Если оно выходит за допустимые пределы, с помощью оператора throw генерируем исключение класса AgeException, который чуть выше определен.

throw AgeException{"Invalid age"};

Класс AgeException специально создан, чтобы инкапсулировать исключение, связанное с возрастом человека. Этот класс просто хранит сообщение об ошибке и определяет метод getMessage для доступа к нему.

В конструкции try-catch для теста определяем пару объектов Person. При передаче некорректного возраста:

Person bob{"Bob", 1500};

будет генерироваться исключение AgeException, и управление перейдем в блок catch, который обрабатывает данный тип исключений:

catch (const AgeException& ex)
{
    std::cout << ex.getMessage() << std::endl;
}

Чтобы не происходило ненужного копирования объекта исключения, в блок catch объект исключения передается по ссылке.

Соответственно консольный вывод программы будет сдедующим

Name: Tom       Age: 38
Invalid age

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

При возникновении исключения обработчики catch проверяются в той последовательности, в которой они определены в коде. И если будет найден первый блок catch, параметр которого соответствует типу исключения, то он выбирается для обработки исключения. Для исключений, которые являются базовыми типами (а не типами классов), необходимо точное совпадение типа исключения с типом параметра в блоке catch. А для исключений-объектов классов при сопоставлении могут применяться неявные преобразования. В этом случае обработчик catch выбирается, если

  • Параметр в catch имеет тот же самый тип, что и исключение (const игнорируется)

  • Тип параметра в catch представляет базовый класс для типа исключения или ссылку на базовый класс (const игнорируется)

  • Исключение и параметр в catch представляют указатели, соответственно объект исключения может быть неявно преобразован к типу параметра (const игнорируется)

Поскольку исключения производных классов неявно преобразуются в тип базового класса, то мы можем перехватывать все исключения, которые представляют базовый и производный типы, с помощью одного обработчика catch.

#include <iostream>

class AgeException
{
public: 
    AgeException(std::string message): message{message}{}
    virtual std::string getMessage() const  // виртуальная функция
    {
        return message;
    }
private:
    std::string message;
};

class MaxAgeException: public AgeException
{
public: 
    MaxAgeException(std::string message, unsigned maxAge): AgeException{message}, maxAge{maxAge}
    {}
    std::string getMessage() const override // переопределяем виртуальную функцию
    {   
        return AgeException::getMessage() + " Max age should be " + std::to_string(maxAge);
    }
private:
    unsigned maxAge;
};

class Person
{
public:
    Person(std::string name, unsigned age)
    {
        if(!age)   // если возраст равен 0
        {
            throw AgeException{"Invalid age"};
        }
        if(age>110)   // если возраст больше 110
        {
            throw MaxAgeException{"Invalid age.", 110};
        }
        this->name = name;
        this->age = age;
    }
    void print() const
    {
        std::cout << "Name: " << name << "\tAge: " << age << std::endl;
    }
private:
    std::string name;
    unsigned age;
};

int main()
{
    try
    {
        Person bob{"Bob", 1500};    // Некорректные данные
        bob.print();
    }
    catch (const AgeException& ex)
    {
        std::cout << ex.getMessage() << std::endl;
    }
}

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

Несмотря на то, что в конструкторе класса Person по отдельности генерируются два этих типа исключения

if(!age)   // если возраст равен 0
{
    throw AgeException{"Invalid age"};
}
if(age>110)   // если возраст больше 110
{
    throw MaxAgeException{"Invalid age.", 110};
}

оба типа исключений мы можем обработать, обрабатывая только исключение базового типа - AgeException:

catch (const AgeException& ex)
{
    std::cout << ex.getMessage() << std::endl;
}

Поскольку функция getMessage - виртуальная и переопределена в MaxAgeException, а параметр в catch передается по ссылке, то при вызове этой функции будет выбрана нужная реализация. И в данном случае на консоль будет выведено:

Invalid age. Max age should be 110

Раздельная обработка исключений

Иногда может потребоваться выполнить раздельную обработку исключений базовых и производных классов, особенно когда необходимо вызвать какие-нибудь функции производных классов, которых нет в базовых. Поскольку объекты исключений могут сопоставляться с параметрами базовых классов в блоке catch, то обработку производных классов надо размещать перед обработкой базовых классов. Например, возьмем выше определенные классы Person, AgeException и MaxAgeException и обработаем типы исключений по-отдельности:

void testPerson(std::string name, unsigned age)
{
    try
    {
        Person person{name, age};
        person.print();
    }
    catch (const MaxAgeException& ex)   // сначала обрабатываем исключение производного типа
    {
        std::cout << "MaxAgeException: " << ex.getMessage() << std::endl;
    }
    catch (const AgeException& ex)  // потом обрабатываем исключение базового типа
    {
        std::cout << "AgeException: " << ex.getMessage() << std::endl;
    }
}
int main()
{
    testPerson("Tom", 0);       // AgeException: Invalid age
    testPerson("Bob", 1000);    // MaxAgeException: Invalid age. Max age should be 110
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850