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

Философия дизайна

Исключения в C++ были созданы с учетом некоторых соображений дизайна. Давайте посмотрим на некоторые из них и попробуем их понять.

(1) Код для обработки ошибок должен быть отделен от обычного кода. Механизм обработки ошибок не должен быть реализован таким образом, что трудно сказать, какая часть кода выполняет фактическую работу. функциональность и какие части кода предназначены для обработки ошибок.

(2) Обработка ошибок должна быть частью самого языка, в отличие от C, где обработка ошибок представлена ​​как часть библиотек.

(3) Он должен поддерживать уничтожение динамической памяти в случае исключений.

(4) Исключения могут распространяться по любым уровням стека.

(5) Один обработчик должен иметь возможность обрабатывать различные виды исключений.

Обработка исключений в C++

Я твердо верю в то, что нужно учиться, поэтому давайте возьмем пример, чтобы понять обработку исключений в C++.

#include <exception>
#include <iostream>

using namespace std;

// Exception class for holding exception related information
// Extends exception class
// More details on class exception towards the end 
class Excp: public exception {};

// A function where we anticipate error might occur
void g() {
    bool error  = false;
    cout << "g() started" << endl;
    if(error) {
        throw Excp();
    }
    cout << "g() ended" << endl;
}

int main() {
    try {
    // Any code that may throw some exception 
    // is called from inside a try block
        cout << "g() called" << endl;
        g();
        cout << "g() returned" << endl;
    } catch (Excp&) {
        cout << "g() failed" << endl;
    }
}

Когда эта программа выполняется, мы получаем следующий вывод:

g() called
g() started
g() ended
g() returned

Поток кода в этом примере ничем не отличается от обычного потока кода. Но все становится интереснее, когда мы меняем значение ошибки на true.

Рассмотрим следующую обновленную версию кода нашего примера.

#include <exception>
#include <iostream>

using namespace std;

class A {
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
};

class Excp: public exception {};

void g() {
    A a;
    bool error  = true;
    cout << "g() started" << endl;
    if(error) {
        throw Excp();
    }
    cout << "g() ended" << endl;
}

int main() {
    try {
        cout << "g() called" << endl;
        g();
        cout << "g() returned" << endl;
    } catch (Excp&) {
        cout << "g() failed" << endl;
    }
}

В этом коде мы изменили значение переменной error на true (это имитирует ошибку в методе g), а также создали экземпляр объекта пользовательского класса A внутри g. Если мы запустим эту программу, мы получим следующий вывод.

g() called
A()
g() started
~A()
g() failed

Интересный? Как только мы наткнулись на это ключевое слово throw, мы, похоже, вернулись из функции. И мы не только вернулись, мы вернулись изящно, правильно раскрутив стек и уничтожив экземпляры объектов.

Но бросок отличается от возвращения. Мы всегда бросаем выражение. Затем это выражение может быть перехвачено любым подходящим обработчиком исключений в стеке. Этот захват осуществляется путем определения блока try-catch. Любое исключение, созданное в блоке try, приведет к правильному выполнению блока catch.

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

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

#include <exception>
#include <iostream>

using namespace std;

class A {
public:
    A() { cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }
};

class Excp: public exception {};

void g(int t) {
    A a;
    cout << "g() started" << endl;
    switch(t) {
        case 1: throw 1;
        case 2: throw 1.5;
        case 3: throw Excp();
        case 4: exception();
        case 5: throw a;
    }
    cout << "g() ended" << endl;
}

int main() {
    int exceptionType = 3;
    try {
        cout << "g() called" << endl;
        g(exceptionType);
        cout << "g() returned" << endl;
    } catch(int) {
        cout << "g threw an int type exception" << endl;
    } catch(double) {
        cout << "g threw a double type exception" << endl;
    } catch (Excp&) {
        cout << "g threw a Excp type exception" << endl;
    } catch (...) {
        cout << "g threw an unknow type of exception" << endl;
    }
}

Вы можете поиграть с этим кодом и изменить значение exceptionType, чтобы увидеть, что разные обработчики срабатывают в разное время.

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

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

Давайте формализуем…

Увидев несколько примеров, давайте теперь более подробно рассмотрим различные компоненты, которые мы только что видели.

Блок try объединяет область, которая может вызывать исключения.

Блок catch — это имя обработчика исключений, которое следует сразу после блока try. Перехват исключения похож на вызов функции. Каждый блок catch должен иметь уникальные параметры.

Мы можем определить несколько блоков catch для блока try, и они сопоставляются в соответствии с их порядком. Если ни один блок catch не соответствует, исключение всплывает до тех пор, пока оно не будет перехвачено каким-либо блоком catch или в конечном итоге не завершит программу.

Выражение, которое мы выбрасываем, обрабатывается так же, как аргумент функции или операнд оператора return. Обратите внимание, что объект исключения создается в магазине Free.

Повторное создание исключения

После перехвата исключения его можно снова сгенерировать. Есть два способа сделать это.

try {} 
catch (Exception& ex) {
	// Do some cleanup
	throw ex;
	// ex would be copied and then destroyed
        // Make sure Exception has copy constructor and destructor
}

И.

try {} 
catch (Exception& ex) {
	// Do some cleanup
	throw;
	// Same ex object is thrown
}

Что это за класс исключений?

Итак, до сих пор я упускал из виду тот факт, что самый первый класс Excp, который мы создали в нашем примере, расширял другой класс с именем exception. Что делает этот класс?

Этот класс exception имеет следующие методы.

class exception {
public:
	exception() throw();
	exception(const exception&) throw();
	exception& operator=(const exception&) throw();
	virtual ~exception() throw();
	virtual const char* what() throw();
};

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

Также наличие throw() указывает на то, что эти методы никогда не будут генерировать исключение.

Завершение…

На этом мы подошли к концу этой статьи. Я надеялся, что ты узнал что-то новое. Если вы хотите продолжить узнавать больше интересного о C++, обязательно ознакомьтесь с моей серией статей in C++.