Throw c виды ошибок

Signals an erroneous condition and executes an error handler.

Contents

  • 1 Syntax
  • 2 Explanation
  • 3 The exception object
  • 4 Stack unwinding
  • 5 Notes
  • 6 Keywords
  • 7 Example
  • 8 Defect reports
  • 9 See also

[edit] Syntax

throw expression (1)
throw (2)

[edit] Explanation

See try-catch block for more information about try and catch (exception handler) blocks

1) First, copy-initializes the exception object from expression

  • This may call the move constructor for rvalue expression. Even if copy initialization selects the move constructor, copy initialization from lvalue must be well-formed, and the destructor must be accessible
(since C++11)
  • This may also call the move constructor for expressions that name local variables or function or catch-clause parameters whose scope does not extend past the innermost enclosing try-block (if any), by same overload resolution as in return statement
(since C++17)
  • The copy/move (since C++11) may be subject to copy elision
then transfers control to the exception handler with the matching type for which the compound statement or member initializer list that follows the keyword try was most recently entered and not exited by this thread of execution.

2) Rethrows the currently handled exception. Abandons the execution of the current catch block and passes control to the next matching exception handler (but not to another catch clause after the same try block: its compound-statement is considered to have been ‘exited’), reusing the existing exception object: no new objects are made. This form is only allowed when an exception is presently being handled (it calls std::terminate if used otherwise). The catch clause associated with a function-try-block must exit via rethrowing if used on a constructor.

See std::terminate and std::unexpected (until C++17) for the handling of errors that arise during exception handling.

[edit] The exception object

The exception object is a temporary object in unspecified storage that is constructed by the throw expression.

The type of the exception object is the static type of expression with top-level cv-qualifiers removed. Array and function types are adjusted to pointer and pointer to function types, respectively. If the type of the exception object would be an incomplete type, an abstract class type, or pointer to incomplete type other than pointer to (cv-qualified) void, the throw-expression is a compile-time error. If the type of expression is a class type, its copy/move (since C++11) constructor and destructor must be accessible even if copy elision takes place.

Unlike other temporary objects, the exception object is considered to be an lvalue argument when initializing the catch clause parameters, so it can be caught by lvalue reference, modified, and rethrown.

The exception object persists until the last catch clause exits other than by rethrowing (if not by rethrowing, it is destroyed immediately after the destruction of the catch clause’s parameter), or until the last std::exception_ptr that references this object is destroyed (in which case the exception object is destroyed just before the destructor of std::exception_ptr returns.

[edit] Stack unwinding

Once the exception object is constructed, the control flow works backwards (up the call stack) until it reaches the start of a try block, at which point the parameters of all associated catch blocks are compared, in order of appearance, with the type of the exception object to find a match (see try-catch for details on this process). If no match is found, the control flow continues to unwind the stack until the next try block, and so on. If a match is found, the control flow jumps to the matching catch block.

As the control flow moves up the call stack, destructors are invoked for all objects with automatic storage duration that are constructed, but not yet destroyed, since the corresponding try-block was entered, in reverse order of completion of their constructors. If an exception is thrown from a destructor of a local variable or of a temporary used in a return statement, the destructor for the object returned from the function is also invoked.

If an exception is thrown from a constructor or (rare) from a destructor of an object (regardless of the object’s storage duration), destructors are called for all fully-constructed non-static non-variant members and base classes, in reverse order of completion of their constructors. Variant members of union-like classes are only destroyed in the case of unwinding from constructor, and if the active member changed between initialization and destruction, the behavior is undefined.

If a delegating constructor exits with an exception after the non-delegating constructor successfully completed, the destructor for this object is called.

(since C++11)

If the exception is thrown from a constructor that is invoked by a new-expression, the matching deallocation function is called, if available.

This process is called stack unwinding.

If any function that is called directly by the stack unwinding mechanism, after initialization of the exception object and before the start of the exception handler, exits with an exception, std::terminate is called. Such functions include destructors of objects with automatic storage duration whose scopes are exited, and the copy constructor of the exception object that is called (if not elided) to initialize catch-by-value arguments.

If an exception is thrown and not caught, including exceptions that escape the initial function of std::thread, the main function, and the constructor or destructor of any static or thread-local objects, then std::terminate is called. It is implementation-defined whether any stack unwinding takes place for uncaught exceptions.

[edit] Notes

When rethrowing exceptions, the second form must be used to avoid object slicing in the (typical) case where exception objects use inheritance:

try
{
    std::string("abc").substr(10); // throws std::out_of_range
}
catch (const std::exception& e)
{
    std::cout << e.what() << '\n';
//  throw e; // copy-initializes a new exception object of type std::exception
    throw;   // rethrows the exception object of type std::out_of_range
}

The throw-expression is classified as prvalue expression of type void. Like any other expression, it may be a sub-expression in another expression, most commonly in the conditional operator:

[edit] Keywords

throw

[edit] Example

#include <iostream>
#include <stdexcept>
 
struct A
{
    int n;
 
    A(int n = 0): n(n) { std::cout << "A(" << n << ") constructed successfully\n"; }
    ~A() { std::cout << "A(" << n << ") destroyed\n"; }
};
 
int foo()
{
    throw std::runtime_error("error");
}
 
struct B
{
    A a1, a2, a3;
 
    B() try : a1(1), a2(foo()), a3(3)
    {
        std::cout << "B constructed successfully\n";
    }
    catch(...)
    {
    	std::cout << "B::B() exiting with exception\n";
    }
 
    ~B() { std::cout << "B destroyed\n"; }
};
 
struct C : A, B
{
    C() try
    {
        std::cout << "C::C() completed successfully\n";
    }
    catch(...)
    {
        std::cout << "C::C() exiting with exception\n";
    }
 
    ~C() { std::cout << "C destroyed\n"; }
};
 
int main () try
{
    // creates the A base subobject
    // creates the a1 member of B
    // fails to create the a2 member of B
    // unwinding destroys the a1 member of B
    // unwinding destroys the A base subobject
    C c;
}
catch (const std::exception& e)
{
    std::cout << "main() failed to create C with: " << e.what();
}

Output:

A(0) constructed successfully
A(1) constructed successfully
A(1) destroyed
B::B() exiting with exception
A(0) destroyed
C::C() exiting with exception
main() failed to create C with: error

[edit] Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
CWG 499 C++98 an array with unknown bound could not be thrown because
its type is incomplete, but an exception object can be
created from the decayed pointer without any problem
apply the type completion
requirement to the
exception object instead
CWG 668 C++98 std::terminate was not called if an exception is thrown
from the destructor of a local non-automatic object
call std::terminate
in this case
CWG 1863 C++11 copy constructor was not required for move-only
exception objects when thrown, but copying allowed later
copy constructor required
CWG 1866 C++98 variant members were leaked on stack unwinding from constructor variant members destroyed
CWG 2176 C++98 throw from a local variable destructor
could skip return value destructor
function return value
added to unwinding

[edit] See also

  • copy elision
  • try-catch block
  • noexcept specifier
  • dynamic exception specifications

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

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

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

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

int ReadAge() {
    int age;
    std::cin >> age;
    if (age < 0 || age >= 128) {
        // Что вернуть в этом случае?
    }
    return age;
}

Что вернуть в случае некорректного возраста? Можно было бы, например, договориться, что в этом случае функция возвращает ноль. Но тогда похожая проверка должна быть и в месте вызова функции:

int main() {
    if (int age = ReadAge(); age == 0) {
        // Произошла ошибка
    } else {
        // Работаем с возрастом age
    }
}

Такая проверка неудобна. Более того, нет никакой гарантии, что в вызывающей функции программист вообще её напишет. Фактически мы тут выбрали некоторое значение функции (ноль), обозначающее ошибку. Это пример подхода к обработке ошибок через коды возврата. Другим примером такого подхода является хорошо знакомая нам функция main. Только она должна возвращать ноль при успешном завершении и что-либо ненулевое в случае ошибки.

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

#include <iostream>

struct WrongAgeException {
    int age;
};

int ReadAge() {
    int age;
    std::cin >> age;
    if (age < 0 || age >= 128) {
        throw WrongAgeException(age);
    }
    return age;
}

Здесь в случае ошибки оператор throw генерирует исключение, которое представлено временным объектом типа WrongAgeException. В этом объекте сохранён для контекста текущий неправильный возраст age. Функция досрочно завершает работу: у неё нет возможности обработать эту ошибку, и она должна сообщить о ней наружу. Поток управления возвращается в то место, откуда функция была вызвана. Там исключение может быть перехвачено и обработано.

Перехват исключения

Мы вызывали нашу функцию ReadAge из функции main. Обработать ошибку в месте вызова можно с помощью блока try/catch:

int main() {
    try {
        age = ReadAge();  // может сгенерировать исключение
        // Работаем с возрастом age
    } catch (const WrongAgeException& ex) {  // ловим объект исключения
        std::cerr << "Age is not correct: " << ex.age << "\n";
        return 1;  // выходим из функции main с ненулевым кодом возврата
    }
    // ...
}

Мы знаем заранее, что функция ReadAge может сгенерировать исключение типа WrongAgeException. Поэтому мы оборачиваем вызов этой функции в блок try. Если происходит исключение, для него подбирается подходящий catch-обработчик. Таких обработчиков может быть несколько. Можно смотреть на них как на набор перегруженных функций от одного аргумента — объекта исключения. Выбирается первый подходящий по типу обработчик и выполняется его код. Если же ни один обработчик не подходит по типу, то исключение считается необработанным. В этом случае оно пробрасывается дальше по стеку — туда, откуда была вызвана текущая функция. А если обработчик не найдётся даже в функции main, то программа аварийно завершается.

Усложним немного наш пример, чтобы из функции ReadAge могли вылетать исключения разных типов. Сейчас мы проверяем только значение возраста, считая, что на вход поступило число. Но предположим, что поток ввода досрочно оборвался, или на входе была строка вместо числа. В таком случае конструкция std::cin >> age никак не изменит переменную age, а лишь возведёт специальный флаг ошибки в объекте std::cin. Наша переменная age останется непроинициализированной: в ней будет лежать неопределённый мусор. Можно было бы явно проверить этот флаг в объекте std::cin, но мы вместо этого включим режим генерации исключений при таких ошибках ввода:

int ReadAge() {
    std::cin.exceptions(std::istream::failbit);
    int age;
    std::cin >> age;
    if (age < 0 || age >= 128) {
        throw WrongAgeException(age);
    }
    return age;
}

Теперь ошибка чтения в операторе >> у потока ввода будет приводить к исключению типа std::istream::failure. Функция ReadAge его не обрабатывает. Поэтому такое исключение покинет пределы этой функции. Поймаем его в функции main:

int main() {
    try {
        age = ReadAge();  // может сгенерировать исключения разных типов
        // Работаем с возрастом age
    } catch (const WrongAgeException& ex) {
        std::cerr << "Age is not correct: " << ex.age << "\n";
        return 1;
    } catch (const std::istream::failure& ex) {
        std::cerr << "Failed to read age: " << ex.what() << "\n";
        return 1;
    } catch (...) {
        std::cerr << "Some other exception\n";
        return 1;
    }
    // ...
}

При обработке мы воспользовались функцией ex.what у исключения типа std::istream::failure. Такие функции есть у всех исключений стандартной библиотеки: они возвращают текстовое описание ошибки.

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

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

Функции и классы стандартной библиотеки в некоторых ситуациях генерируют исключения особых типов. Все такие типы выстроены в иерархию наследования от базового класса std::exception. Иерархия классов позволяет писать обработчик catch сразу на группу ошибок, которые представлены базовым классом: std::logic_error, std::runtime_error и т. д.

Вот несколько примеров:

  1. Функция at у контейнеров std::array, std::vector и std::deque генерирует исключение std::out_of_range при некорректном индексе.

  2. Аналогично, функция at у std::map, std::unordered_map и у соответствующих мультиконтейнеров генерирует исключение std::out_of_range при отсутствующем ключе.

  3. Обращение к значению у пустого объекта std::optional приводит к исключению std::bad_optional_access.

  4. Потоки ввода-вывода могут генерировать исключение std::ios_base::failure.

Исключения в конструкторах

В параграфе 3.1 мы написали класс Time. Этот класс должен был соблюдать инвариант на значение часов, минут и секунд: они должны были быть корректными. Если на вход конструктору класса Time передавались некорректные значения, мы приводили их к корректным, используя деление с остатком.

Более правильным было бы сгенерировать в конструкторе исключение. Таким образом мы бы явно передали сообщение об ошибке во внешнюю функцию, которая пыталась создать объект.

class Time {
private:
    int hours, minutes, seconds;

public:
    // Заведём класс для исключения и поместим его внутрь класса Time как в пространство имён
    class IncorrectTimeException {
    };

    Time::Time(int h, int m, int s) {
        if (s < 0 || s > 59 || m < 0 || m > 59 || h < 0 || h > 23) {
            throw IncorrectTimeException();
        }
        hours = h;
        minutes = m;
        seconds = s;
    }

    // ...
};

Генерировать исключения в конструкторах — совершенно нормальная практика. Однако не следует допускать, чтобы исключения покидали пределы деструкторов. Чтобы понять причины, посмотрим подробнее, что происходит при генерации исключения.

Свёртка стека

Вспомним класс Logger из предыдущего параграфа. Посмотрим, как он ведёт себя при возникновении исключения. Воспользуемся в этом примере стандартным базовым классом std::exception, чтобы не писать свой класс исключения.

#include <exception>
#include <iostream>

void f() {
    std::cout << "Welcome to f()!\n";
    Logger x;
    // ...
    throw std::exception();  // в какой-то момент происходит исключение
}

int main() {
    try {
        Logger y;
        f();
    } catch (const std::exception&) {
        std::cout << "Something happened...\n";
        return 1;
    }
}

Мы увидим такой вывод:

Logger(): 1
Welcome to f()!
Logger(): 2
~Logger(): 2
~Logger(): 1
Something happened...

Сначала создаётся объект y в блоке try. Затем мы входим в функцию f. В ней создаётся объект x. После этого происходит исключение. Мы должны досрочно покинуть функцию. В этот момент начинается свёртка стека (stack unwinding): вызываются деструкторы для всех созданных объектов в самой функции и в блоке try, как если бы они вышли из своей области видимости. Поэтому перед обработчиком исключения мы видим вызов деструктора объекта x, а затем — объекта y.

Аналогично, свёртка стека происходит и при генерации исключения в конструкторе. Напишем класс с полем Logger и сгенерируем нарочно исключение в его конструкторе:

#include <exception>
#include <iostream>

class C {
private:
    Logger x;

public:
    C() {
        std::cout << "C()\n";
        Logger y;
        // ...
        throw std::exception();
    }

    ~C() {
        std::cout << "~C()\n";
    }
};

int main() {
    try {
        C c;
    } catch (const std::exception&) {
        std::cout << "Something happened...\n";
    }
}

Вывод программы:

Logger(): 1  // конструктор поля x
C()
Logger(): 2  // конструктор локальной переменной y
~Logger(): 2  // свёртка стека: деструктор y
~Logger(): 1  // свёртка стека: деструктор поля x
Something happened...

Заметим, что деструктор самого класса C не вызывается, так как объект в конструкторе не был создан.

Механизм свёртки стека гарантирует, что деструкторы для всех созданных автоматических объектов или полей класса в любом случае будут вызваны. Однако он полагается на важное свойство: деструкторы самих классов не должны генерировать исключений. Если исключение в деструкторе произойдёт в момент свёртки стека при обработке другого исключения, то программа аварийно завершится.

Пример с динамической памятью

Подчеркнём, что свёртка стека работает только с автоматическими объектами. В этом нет ничего удивительного: ведь за временем жизни объектов, созданных в динамической памяти, программист должен следить самостоятельно. Исключения вносят дополнительные сложности в ручное управление динамическими объектами:

void f() {
    Logger* ptr = new Logger();  // конструируем объект класса Logger в динамической памяти
    // ...
    g();  // вызываем какую-то функцию
    // ...
    delete ptr;  // вызываем деструктор и очищаем динамическую память
}

На первый взгляд кажется, что в этом коде нет ничего опасного: delete вызывается в конце функции. Однако функция g может сгенерировать исключение. Мы не перехватываем его в нашей функции f. Механизм свёртки уберёт со стека лишь сам указатель ptr, который является автоматической переменной примитивного типа. Однако он ничего не сможет сделать с объектом в памяти, на которую ссылается этот указатель. В логе мы увидим только вызов конструктора класса Logger, но не увидим вызова деструктора. Нам придётся обработать исключение вручную:

void f() {
    Logger* ptr = new Logger();
    // ...
    try {
        g();
    } catch (...) {  // ловим любое исключение
        delete ptr;  // вручную удаляем объект
        throw;  // перекидываем объект исключения дальше
    }
    // ...
    delete ptr;

}

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

Согласитесь, этот код очень далёк от совершенства. При непосредственной работе с объектами в динамической памяти нам приходится оборачивать в try/catch любую конструкцию, из которой может вылететь исключение. Понятно, что такой код чреват ошибками. В параграфе 3.6 мы узнаем, как с точки зрения C++ следует работать с такими ресурсами, как память.

Гарантии безопасности исключений

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

Не вдаваясь в детали, давайте посмотрим, как могла бы выглядеть функция добавления элемента.

template <typename T>
class List {
private:
    struct Node {  // узел двусвязного списка
        T element;
        Node* prev = nullptr;  // предыдущий узел
        Node* next = nullptr;  // следующий узел
    };

    Node* first = nullptr;  // первый узел списка
    Node* last = nullptr;  // последний узел списка
    int elementsCount = 0;

public:
    // ...

    size_t Size() const {
        return elementsCount;
    }

    void PushBack(const T& elem) {
        ++elementsCount;

        // Конструируем в динамической памяти новой узел списка
        Node* node = new Node(elem, last, nullptr);

        // Связываем новый узел с остальными узлами
        if (last != nullptr) {
            last->next = node;
        } else {
            first = node;
        }
        last = node;
    }
};

Не будем здесь рассматривать другие функции класса — конструкторы, деструктор, оператор присваивания… Рассмотрим функцию PushBack. В ней могут произойти такие исключения:

  1. Выражение new может сгенерировать исключение std::bad_alloc из-за нехватки памяти.

  2. Конструктор копирования класса T может сгенерировать произвольное исключение. Этот конструктор вызывается при инициализации поля element создаваемого узла в конструкторе класса Node. В этом случае new ведёт себя как транзакция: выделенная перед этим динамическая память корректно вернётся системе.

Эти исключения не перехватываются в функции PushBack. Их может перехватить код, из которого PushBack вызывался:

#include <iostream>

class C;  // какой-то класс

int main() {
    List<C> data;
    C element;

    try {
        data.PushBack(element);
    } catch (...) {  // не получилось добавить элемент
        std::cout << data.Size() << "\n";  // внезапно 1, а не 0
    }

    // работаем дальше с data
}

Наша функция PushBack сначала увеличивает счётчик элементов, а затем выполняет опасные операции. Если происходит исключение, то в классе List нарушается инвариант: значение счётчика elementsCount перестаёт соответствовать реальности. Можно сказать, что функция PushBack не даёт гарантий безопасности.

Всего выделяют четыре уровня гарантий безопасности исключений (exception safety guarantees):

  1. Гарантия отсутствия сбоев. Функции с такими гарантиями вообще не выбрасывают исключений. Примерами могут служить правильно написанные деструктор и конструктор перемещения, а также константные функции вида Size.

  2. Строгая гарантия безопасности. Исключение может возникнуть, но от этого объект нашего класса не поменяет состояние: количество элементов останется прежним, итераторы и ссылки не будут инвалидированы и т. д.

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

  4. Отсутствие гарантий. Это довольно опасная категория: при возникновении исключений могут нарушаться инварианты.

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

Переместим в нашей функции PushBack изменение счётчика в конец:

    void PushBack(const T& elem) {
        Node* node = new Node(elem, last, nullptr);

        if (last != nullptr) {
            last->next = node;
        } else {
            first = node;
        }
        last = node;

        ++elementsCount;  // выполнится только если раньше не было исключений
    }

Теперь такая функция соответствует строгой гарантии безопасности.

В документации функций из классов стандартной библиотеки обычно указано, какой уровень гарантии они обеспечивают. Рассмотрим, например, гарантии безопасности класса std::vector.

  • Деструктор, функции empty, size, capacity, а также clear предоставляют гарантию отсутствия сбоев.

  • Функции push_back и resize предоставляют строгую гарантию.

  • Функция insert предоставляет лишь базовую гарантию. Можно было бы сделать так, чтобы она предоставляла строгую гарантию, но за это пришлось бы заплатить её эффективностью: при вставке в середину вектора пришлось бы делать реаллокацию.

Функции класса, которые гарантируют отсутствие сбоев, следует помечать ключевым словом noexcept:

class C {
public:
    void f() noexcept {
        // ...
    }
};

С одной стороны, эта подсказка позволяет компилятору генерировать более эффективный код. С другой — эффективно обрабатывать объекты таких классов в стандартных контейнерах. Например, std::vector<C> при реаллокации будет использовать конструктор перемещения класса C, если он помечен как noexcept. В противном случае будет использован конструктор копирования, который может быть менее эффективен, но зато позволит обеспечить строгую гарантию безопасности при реаллокации.

One of the advantages of C++ over C is Exception Handling. Exceptions are runtime anomalies or abnormal conditions that a program encounters during its execution. There are two types of exceptions: a)Synchronous, b)Asynchronous (i.e., exceptions which are beyond the program’s control, such as disc failure, keyboard interrupts etc.). C++ provides the following specialized keywords for this purpose:
try: Represents a block of code that can throw an exception.
catch: Represents a block of code that is executed when a particular exception is thrown.
throw: Used to throw an exception. Also used to list the exceptions that a function throws but doesn’t handle itself.

Why Exception Handling? 
The following are the main advantages of exception handling over traditional error handling:

1) Separation of Error Handling code from Normal Code: In traditional error handling codes, there are always if-else conditions to handle errors. These conditions and the code to handle errors get mixed up with the normal flow. This makes the code less readable and maintainable. With try/catch blocks, the code for error handling becomes separate from the normal flow.

2) Functions/Methods can handle only the exceptions they choose: A function can throw many exceptions, but may choose to handle some of them. The other exceptions, which are thrown but not caught, can be handled by the caller. If the caller chooses not to catch them, then the exceptions are handled by the caller of the caller. 
In C++, a function can specify the exceptions that it throws using the throw keyword. The caller of this function must handle the exception in some way (either by specifying it again or catching it).

3) Grouping of Error Types: In C++, both basic types and objects can be thrown as exceptions. We can create a hierarchy of exception objects, group exceptions in namespaces or classes and categorize them according to their types.
 

C++ Exceptions:

When executing C++ code, different errors can occur: coding errors made by the programmer, errors due to wrong input, or other unforeseeable things.

When an error occurs, C++ will normally stop and generate an error message. The technical term for this is: C++ will throw an exception (error).

C++ try and catch:

Exception handling in C++ consists of three keywords: try, throw and catch:

The try statement allows you to define a block of code to be tested for errors while it is being executed.

The throw keyword throws an exception when a problem is detected, which lets us create a custom error.

The catch statement allows you to define a block of code to be executed if an error occurs in the try block.

The try and catch keywords come in pairs:

We use the try block to test some code: If the value of a variable “age” is less than 18, we will throw an exception, and handle it in our catch block.

In the catch block, we catch the error if it occurs and do something about it. The catch statement takes a single parameter. So, if the value of age is 15 and that’s why we are throwing an exception of type int in the try block (age), we can pass “int myNum” as the parameter to the catch statement, where the variable “myNum” is used to output the value of age.

If no error occurs (e.g. if age is 20 instead of 15, meaning it will be greater than 18), the catch block is skipped.

Exception Handling in C++

1) The following is a simple example to show exception handling in C++. The output of the program explains the flow of execution of try/catch blocks. 

CPP

#include <iostream>

using namespace std;

int main()

{

   int x = -1;

   cout << "Before try \n";

   try {

      cout << "Inside try \n";

      if (x < 0)

      {

         throw x;

         cout << "After throw (Never executed) \n";

      }

   }

   catch (int x ) {

      cout << "Exception Caught \n";

   }

   cout << "After catch (Will be executed) \n";

   return 0;

}

Output: 

Before try
Inside try
Exception Caught
After catch (Will be executed)

2) There is a special catch block called the ‘catch all’ block, written as catch(…), that can be used to catch all types of exceptions. For example, in the following program, an int is thrown as an exception, but there is no catch block for int, so the catch(…) block will be executed. 

CPP

#include <iostream>

using namespace std;

int main()

{

    try  {

       throw 10;

    }

    catch (char *excp)  {

        cout << "Caught " << excp;

    }

    catch (...)  {

        cout << "Default Exception\n";

    }

    return 0;

}

Output: 

Default Exception

3) Implicit type conversion doesn’t happen for primitive types. For example, in the following program, ‘a’ is not implicitly converted to int. 

CPP

#include <iostream>

using namespace std;

int main()

{

    try  {

       throw 'a';

    }

    catch (int x)  {

        cout << "Caught " << x;

    }

    catch (...)  {

        cout << "Default Exception\n";

    }

    return 0;

}

Output: 

Default Exception

4) If an exception is thrown and not caught anywhere, the program terminates abnormally. For example, in the following program, a char is thrown, but there is no catch block to catch the char.  

CPP

#include <iostream>

using namespace std;

int main()

{

    try  {

       throw 'a';

    }

    catch (int x)  {

        cout << "Caught ";

    }

    return 0;

}

Output: 

terminate called after throwing an instance of 'char'

This application has requested the Runtime to terminate it in an 
unusual way. Please contact the application's support team for 
more information.

We can change this abnormal termination behavior by writing our own unexpected function.
5) A derived class exception should be caught before a base class exception. See this for more details.
6) Like Java, the C++ library has a standard exception class which is the base class for all standard exceptions. All objects thrown by the components of the standard library are derived from this class. Therefore, all standard exceptions can be caught by catching this type
7) Unlike Java, in C++, all exceptions are unchecked, i.e., the compiler doesn’t check whether an exception is caught or not (See this for details). So, it is not necessary to specify all uncaught exceptions in a function declaration. Although it’s a recommended practice to do so. For example, the following program compiles fine, but ideally the signature of fun() should list the unchecked exceptions. 

CPP

#include <iostream>

using namespace std;

void fun(int *ptr, int x)

{

    if (ptr == NULL)

        throw ptr;

    if (x == 0)

        throw x;

}

int main()

{

    try {

       fun(NULL, 0);

    }

    catch(...) {

        cout << "Caught exception from fun()";

    }

    return 0;

}

Output: 

Caught exception from fun()

A better way to write the above code: 

CPP

#include <iostream>

using namespace std;

void fun(int *ptr, int x) throw (int *, int)

{

    if (ptr == NULL)

        throw ptr;

    if (x == 0)

        throw x;

}

int main()

{

    try {

       fun(NULL, 0);

    }

    catch(...) {

        cout << "Caught exception from fun()";

    }

    return 0;

}

Note : The use of Dynamic Exception Specification has been deprecated since C++11. One of the reasons for it may be that it can randomly abort your program. This can happen when you throw an exception of another type which is not mentioned in the dynamic exception specification. Your program will abort itself because in that scenario, it calls (indirectly) terminate(), which by default calls abort().

Output: 

Caught exception from fun()

8) In C++, try/catch blocks can be nested. Also, an exception can be re-thrown using “throw; “. 

CPP

#include <iostream>

using namespace std;

int main()

{

    try {

        try {

            throw 20;

        }

        catch (int n) {

            cout << "Handle Partially ";

            throw;

        }

    }

    catch (int n) {

        cout << "Handle remaining ";

    }

    return 0;

}

Output: 

Handle Partially Handle remaining

A function can also re-throw a function using the same “throw; ” syntax. A function can handle a part and ask the caller to handle the remaining.
9) When an exception is thrown, all objects created inside the enclosing try block are destroyed before the control is transferred to the catch block.

CPP

#include <iostream>

using namespace std;

class Test {

public:

    Test() { cout << "Constructor of Test " << endl; }

    ~Test() { cout << "Destructor of Test " << endl; }

};

int main()

{

    try {

        Test t1;

        throw 10;

    }

    catch (int i) {

        cout << "Caught " << i << endl;

    }

}

Output: 

Constructor of Test
Destructor of Test
Caught 10

10) You may like to try Quiz on Exception Handling in C++.
Please write comments if you find anything incorrect, or you want to share more information about the topic discussed above.
 

Время на прочтение
5 мин

Количество просмотров 24K

Всем привет. Продолжаем исследовать новые возможности C# 7. Уже были рассмотрены такие темы как: сопоставление с образцом, локальные функции, кортежи. Сегодня поговорим про Throw.

В C# throw всегда был оператором. Поскольку throw — это оператор, а не выражение, существуют конструкции в C#, в которых нельзя использовать его.

  • в операторе Null-Coalescing (??)
  • в лямбда выражении
  • в условном операторе (?:)
  • в теле выражений (expression-bodied)

Чтобы исправить данные проблемы, C# 7 вводит выражения throw. Синтаксис остался таким же, как всегда использовался для операторов throw. Единственное различие заключается в том, что теперь их можно использовать в большом количестве случаев.
Давайте рассмотрим, в каких местах throw выражения будет лучше использовать. Поехали!

Тернарные операторы

До 7 версии языка C#, использование throw в тернарном операторе запрещалось, так как он был оператором. В новой версии С#, throw используется как выражение, следовательно мы можем добавлять его в тернарный оператор.

var customerInfo = HasPermission()
? ReadCustomer()
: throw new SecurityException("permission denied");

Вывод сообщения об ошибке при проверке на null

«Ссылка на объект не указывает на экземпляр объекта» и «Объект Nullable должен иметь значение», являются двумя наиболее распространенными ошибками в приложениях C#. С помощью выражений throw легче дать более подробное сообщение об ошибке:


var age = user.Age ?? throw new InvalidOperationException("user age must be initialized");

Вывод сообщения об ошибке в методе Single()

В процессе борьбы с ошибками проверок на null, в логах можно видеть наиболее распространенное и бесполезное сообщение об ошибке: «Последовательность не содержит элементов». С появлением LINQ, программисты C# часто используют методы Single() и First(), чтобы найти элемент в списке или запросе. Несмотря на то, что эти методы являются краткими, при возникновении ошибки не дают детальной информации о том, какое утверждение было нарушено.

Throw выражения обеспечивают простой шаблон для добавления полной информации об ошибках без ущерба для краткости:

var customer = dbContext.Orders.Where(o => o.Address == address)
                               .Select(o => o.Customer)
                               .Distinct()
                               .SingleOrDefault()
                               ?? throw new InvalidDataException($"Could not find an order for address '{address}'");

Вывод сообщения об ошибке при конвертации

В C# 7 шаблоны типа предлагают новые способы приведения типов. С помощью выражений throw, можно предоставить конкретные сообщения об ошибках:

var sequence = arg as IEnumerable
?? throw new ArgumentException("Must be a sequence type", nameof(arg));

var invariantString = arg is IConvertible c
    ? c.ToString(CultureInfo.InvariantCulture)
    : throw new ArgumentException($"Must be a {nameof(IConvertible)} type", nameof(arg));

Выражения в теле методов

Throw выражения предлагают наиболее сжатый способ реализовать метод с выбросом ошибки:

class ReadStream : Stream
{
  ...
  override void Write(byte[] buffer, int offset, int count) =>
  throw new NotSupportedException("read only");
  ...
}

Проверка на Dispose

Хорошо управляемые классы IDisposable бросают ObjectDisposedException на большинство операций после их удаления. Throw выражения могут сделать эти проверки более удобными и менее громоздкими:

class DatabaseContext : IDisposable
{
  private SqlConnection connection;

  private SqlConnection Connection => this.connection
          ?? throw new ObjectDisposedException(nameof(DatabaseContext));

  public T ReadById(int id)
  {
    this.Connection.Open();
    ...
  }

  public void Dispose()
  {
    this.connection?.Dispose();
    this.connection = null;
  }
}

LINQ

LINQ обеспечивает идеальную настройку, чтобы сочетать многие из вышеупомянутых способов использования. С тех пор, как он был выпущен в третьей версии C#, LINQ изменил стиль программирования на C# в сторону ориентированного на выражения, а не на операторы. Исторически LINQ часто заставлял разработчиков делать компромиссы между добавлением значимых утверждений и исключений их из кода, оставаясь в синтаксисе сжатого выражения, который лучше всего работает с лямбда выражениями. Throw выражения решают эту проблему!

var awardRecipients = customers.Where(c => c.ShouldReceiveAward)
                       // concise inline LINQ assertion with .Select!
                       .Select(c => c.Status == Status.None
                       ? throw new InvalidDataException($"Customer {c.Id} has no status and should not be an award recipient")
                       : c)
                       .ToList();

Unit тестирование

Также, throw выражения хорошо подходят при написании неработающих методов и свойств (заглушек), которые планируются покрыть с помощью тестов. Поскольку эти члены обычно бросают NotImplementedException, можно сэкономить некоторое место и время.


public class Customer
{
  // ...

  public string FullName => throw new NotImplementedException();

  public Order GetLatestOrder() => throw new NotImplementedException();
  public void ConfirmOrder(Order o) => throw new NotImplementedException();
  public void DeactivateAccount() => throw new NotImplementedException();
}

Типичная проверка в конструкторе


public ClientService(
IClientsRepository clientsRepository,
IClientsNotifications clientsNotificator)
{
  if (clientsRepository == null)
  {
    throw new ArgumentNullException(nameof(clientsRepository));
  }
  if (clientsNotificator == null)
  {
    throw new ArgumentNullException(nameof(clientsNotificator));
  }

  this.clientsRepository = clientsRepository;
  this.clientsNotificator = clientsNotificator;
}

Всем лень писать столько строчек кода для проверки, теперь, если использовать возможности C# 7, можно написать выражения. Это позволит вам переписать такой код.

public ClientService(
IClientsRepository clientsRepository,
IClientsNotifications clientsNotificator)
{
  this.clientsRepository = clientsRepository ?? throw new ArgumentNullException(nameof(clientsRepository));
  this.clientsNotificator = clientsNotificator ?? throw new ArgumentNullException(nameof(clientsNotificator));
}

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

Сеттеры свойств

Throw выражения также позволяют сделать свойства объектов более короткими.


public string FirstName
{
  set
  {
    if (value == null)
      throw new ArgumentNullException(nameof(value));
    _firstName = value;
  }
}

Можно сделать еще короче, используя оператор Null-Coalescing (??).

public string FirstName
{
  set
  {
    _firstName = value ?? throw new ArgumentNullException(nameof(value));
  }
}

или даже использовать тело выражения для методов доступа (геттер, сеттер)

public string FirstName
{
  set => _firstName = value ?? throw new ArgumentNullException(nameof(value));
}

Давайте посмотрим, во что разворачивается данный код компилятором:

private string _firstName;
public string FirstName
{
   get
   {
     return this._firstName;
   }
   set
   {
     string str = value;
     if (str == null)
        throw new ArgumentNullException();
     this._firstName = str;
   }
}

Как мы видим, компилятор сам привел к той версии, которую мы писали в самом начале пункта. Следовательно, не надо писать лишний код, компилятор сделает это за нас.

Заключение.

Throw выражения помогают писать меньший код и использовать исключения в выражениях-членах (expression-bodied). Это всего лишь языковая функция, а не что-то основное в языковой среде исполнения. Хотя throw выражения помогают писать более короткий код, это не серебряная пуля или лекарство от всех болезней. Используйте throw выражения только тогда, когда они могут вам помочь.

Понятие исключительной ситуации. Блок try…catch. Оператор throw. Примеры использования


Содержание

  • 1. Типы ошибок, которые могут возникать в программах
  • 2. Понятие исключительной ситуации
  • 3. Понятие исключения
  • 4. Средства языка C++ для обработки исключительных ситуаций. Общая форма конструкции try…catch. Назначение
  • 5. Оператор throw. Назначение
  • 6. Примеры использования блока try…catch
  • 7. Пример использования блока try…catch в функции
  • 8. Пример программы, которая генерирует исключение в одной функции, а перехватывает его в другой функции
  • 9. Использование блока catch(…). Перехват всех возможных исключительных ситуаций. Пример
  • Связанные темы

Поиск на других ресурсах:

1. Типы ошибок, которые могут возникать в программах

В программах на C++ могут возникать ошибки. Различают три типа ошибок, которые могут возникать в программах:

  • синтаксические. Это ошибки в синтаксисе языка C++. Они могут встречаться в именах операторов, функций, разделителей и т.д. В этом случае компилятор определяет наличие синтаксической ошибки и выдает соответствующее сообщение. В результате исполняющий (*.exe) файл не создается и программа не выполняется;
  • логические. Это ошибки программиста, которые сложно обнаружить на этапе разработки программы. Эти ошибки обнаруживаются на этапе выполнения во время тестирования работы программы. Логические ошибки можно обнаружить только по результатам работы программы. Примером логических ошибок может быть неправильная работа с указателями в случаях выделения/освобождения памяти;
  • ошибки времени выполнения. Такие ошибки возникают во время работы программы. Ошибки времени выполнения могут быть логическими ошибками программиста, ошибками внешних событий (например, нехватка оперативной памяти), неверным вводом данных пользователем и т.п. В результате возникновения ошибки времени выполнения, программа приостанавливает свою работу. Поэтому, важно перехватить эту ошибку и правильно обработать ее для того, чтобы программа продолжила свою работу без остановки.

Данная тема освещает применение механизма перехвата ошибок времени выполнения.

 

2. Понятие исключительной ситуации

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

Примеры действий в программе, которые могут привести к возникновению исключительных ситуаций:

  • деление на нуль;
  • нехватка оперативной памяти при использовании оператора new для ее выделения (или другой функции);
  • доступ к элементу массива за его пределами (ошибочный индекс);
  • переполнение значения для некоторого типа;
  • взятие корня из отрицательного числа;
  • другие ситуации.

 

3. Понятие исключения

В языке C++ исключение – это специальный объект класса или значение базового типа, который описывает (определяет) конкретную исключительную ситуацию и соответствующим образом обрабатывается.

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

 

4. Средства языка C++ для обработки исключительных ситуаций. Общая форма конструкции try…catch. Назначение

Язык программирования C++ дает возможность перехватывать исключительные ситуации и соответствующим образом их обрабатывать.

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

Для перехвата и обработки исключительных ситуаций в языке C++ введена конструкция try…catch, которая имеет следующую общую форму:

try {
  // тело блока try
  // ...
  // генерирование исключения оператором throw
}
catch(type1 argument1)
{
  // тело блока catch
}
catch(type2 argument2)
{
  // тело блока catch
}
...
catch(typeN argumentN)
{
  // тело блока catch
}

где

  • type1, type2, …, typeN – соответственно тип аргументов argument1, argument2, …, argumentN.

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

В блоке try могут быть размещены операторы и функции. Если в блоке try генерируется соответствующая исключительная ситуация, то она перехватывается соответствующим блоком catch. Выбор того или иного блока catch осуществляется в зависимости от типа исключительной ситуации. После возникновения исключительной ситуации определенного типа, вызывается блок catch с таким самым типом аргумента. Аргумент принимает некоторое значение, которое соответствующим образом обрабатывается (выводится на экран сообщение об ошибке и т.п.).

Если в блоке try возникнет исключительная ситуация, которая не предусмотрена блоком catch, то вызывается стандартная функция terminate(), которая по умолчанию вызовет функцию abort(). Эта стандартная функция останавливает выполнение программы.

 

5. Оператор throw. Назначение

Чтобы в блоке try сгенерировать исключительную ситуацию, нужно использовать оператор throw. Оператор throw может быть вызван внутри блока try или внутри функции, которая вызывается из блока try.

Общая форма оператора throw следующая

throw исключение;

В результате выполнения оператора throw генерируется исключение некоторого типа. Это исключение должно быть обработано в блоке catch.

 

6. Примеры использования блока try…catch

Пример 1. Демонстрируется использование блока try…catch для обработки выражения:

В данном выражении в трех случаях может возникнуть исключительная ситуация:

  • корень из отрицательного числа a, если a<0;
  • корень из отрицательного числа b, если b<0;
  • деление на 0, если b=0.

Поэтому, в блоке try…catch нужно обработать эти три случая.

Текст программы типа Console Application следующий:

#include <iostream>
using namespace std;

void main()
{
  // обработка выражения sqrt(a)/sqrt(b)
  double a, b;
  cout << "a = ";
  cin >> a;

  cout << "b = ";
  cin >> b;

  double c;

  try { // начало блока try
    if (b == 0)
      throw 1;
    if (b < 0)
      throw 2;
    if (a < 0)
      throw 2;

    c = sqrt(a) / sqrt(b);
    cout << "c = " << c << endl;
  }
  catch (int e) // перехват ошибки
  {
    if (e == 1)
      cout << "Division by 0." << endl;
    if (e == 2)
      cout << "Negative root." << endl;
  }
}

Результат работы программы

a = 5
b = 0
Division by 0.

После применения блока try..catch работа программы не прекращается.

Пример 2. Другой вариант обработки выражения из примера 1. Здесь блок try…catch содержит два оператора catch.

#include <iostream>
using namespace std;

void main()
{
  // обработка выражения sqrt(a)/sqrt(b) - вариант 2
  double a, b;
  cout << "a = ";
  cin >> a;

  cout << "b = ";
  cin >> b;

  double c;
  string s;

  try { // начало блока try
    if (b == 0)
      throw "Division by 0.";
    if (b < 0)
      throw "Negative root.";
    if (a < 0)
      throw "Negative root.";

    // если исключительных ситуаций нет, то продолжить вычисления
    c = sqrt(a) / sqrt(b);
    cout << "c = " << c << endl;
  }
  catch (int e) // перехват ошибки типа int
  {
    if (e == 1)
      cout << "Division by 0." << endl;
    if (e == 2)
      cout << "Negative root." << endl;
  }
  catch (const char* e) // перехват ошибки типа const char*
  {
    cout << e << endl;
  }
}

 



7. Пример использования блока try…catch в функции

Условие задачи. Написать функцию вычисления значения по заданной строке символов, которая есть записью этого числа в десятичной системе исчисления. Предусмотреть случай выхода за границы диапазона, определяемого типом int. Необходимо использовать механизм исключений.

Текст программы для приложения типа Console Application следующий

#include <iostream>
using namespace std; 

// Функция вычисления значения по заданной строке символов
int StrToInt(const char* str)
{
  char s[20];
  int t, i;
  long res = 0; // результат возврата из функции
  int len = strlen(str);

  try
  {
    t = 1;
    if (str[0] == '-') 
      t = -1; // проверка, первый ли символ '-'

    // цикл конвертирования строки в число типа int
    i = len - 1;
    while (i >= 0)
    {
      if (str[i] == '-')
      {
        if (i == 0) break; // если перший символ '-', то все в порядке
        else throw "Bad position of minus.";
      }

      // если в строке недопустимые символы, то сгенерировать исключение
      if (str[i] < '0') throw "Bad symbols";
      if (str[i] > '9') throw "Bad symbols";

      res = res + (str[i] - '0')*t;

      t *= 10;
      i--;
    }

    // если результат выходит за пределы диапазона для 32-разрядных
    // целочисленных значений, то сгенерировать соответствующее исключения
    if (res > INT32_MAX)
      throw "Out of range.";
    if (res < INT32_MIN)
      throw "Out of range.";
    return res;
  }
  catch (const char* e)
  {
    cout << e << endl;
    return 0;
  }
}

void main()
{
  int d;
  d = StrToInt("125");
  cout << "d = " << d;
}

Вышеприведенная программа может быть переписана так, что блок try…catch размещается в функции main(), как показано ниже

#include <iostream>
using namespace std; 

// Функция вычисления значения по заданной строке символов
int StrToInt2(const char* str)
{
  char s[20];
  int t, i;
  long res = 0; // результат работы функции
  int len = strlen(str);

  t = 1;
  if (str[0] == '-') t = -1;

  i = len - 1;
  while (i >= 0)
  {
    if (str[i] == '-')
    {
      if (i == 0) break; // если первый символ '-', то все в порядке
      else throw "Bad position of minus."; // иначе, сгенерировать исключение
    }

    if (str[i] < '0') throw "Bad symbols";
    if (str[i] > '9') throw "Bad symbols";

    res = res + (str[i] - '0')*t;
    t *= 10;
    i--;
  }

  // если результат выходит за пределы диапазона для 32-разрядных
  // целочисленных значений, то сгенерировать соответствующее исключения
  if (res > INT32_MAX)
    throw "Out of range.";
  if (res < INT32_MIN)
    throw "Out of range.";
  return res;
}

void main()
{
  int d;

  // блок try...catch размещается в функции main() высшего уровня,
  // а исключение генерируется в функции StrToInt2() нижнего уровня
  try {
    d = StrToInt2("19125");
    cout << "d = " << d;
  }
  catch (const char* e)
  {
    cout << e << endl;
  }
}

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

Результат выполнения программы

d = 19125

Если вызов функции StrToInt2() перенести за пределы оператора try

void main()
{
  int d;

  try {
    //d = StrToInt2("19125");
    //cout << "d = " << d;
  }
  catch (const char* e)
  {
    cout << e << endl;
  }

  // вызов функции за пределами оператора try
  d = StrToInt2("в19125");
}

то исключительные ситуации в функции StrToInt2() обрабатываться не будут. При возникновении исключительной ситуации в функции StrToInt2() компилятор сгенерирует собственную ошибку

Exception Unhandled

что означает, что исключение необработано.

 

8. Пример программы, которая генерирует исключение в одной функции, а перехватывает его в другой функции

В примере, в функции нижнего уровня GenerateException() генерируется исключение типа const char*. Функция проверяет допустимые границы входного параметра index.

В функции верхнего уровня ProcessException() происходит вызов функции GenerateException(). Этот вызов взят в блок try.

Текст программы следующий:

#include <iostream>
using namespace std;

// Пример Программы, которая генерирует исключение в одной функции, а перехватывает его в другой
// Функция генерирует исключение "Out of index",
// если значение index находится за пределами диапазона 0..9
void GenerateException(int index)
{
  if ((index < 0) || (index > 9))
    throw "Out of index";
}

// Функция, которая перехватывает исключение "Out of index"
void ProcessException()
{
  int index;
  cout << "index = ";
  cin >> index;

  // 1. Вызвать исключительную ситуацию без обработки,
  // компилятор выдаст сообщение "Exception unhandled"
  // GenerateException(-3);

  // 2. Вызвать исключительную ситуацию с обработкой блоком try...catch
  try {
    GenerateException(index); // вызов функции, которая генерирует исключение
    cout << "OK!" << endl; // если index в пределах 0..9, то OK!
  }
  catch (const char* e)
  {
    cout << "Error: " << e << endl;
  }
}

void main()
{
  ProcessException();
}

Результат выполнения программы

index = -5
Error: Out of index

 

9. Использование блока catch(…). Перехват всех возможных исключительных ситуаций. Пример

Бывают случаи, когда нужно перехватить все исключительные ситуации подряд. Для этого, в C++ используется блок catch(…), который имеет следующую общую форму

catch(...)
{
  // Обработка всех исключительных ситуаций
  // ...
}

Пример. В примере демонстрируется использование блока catch(…) для обработки ситуаций любого типа.

В программе реализованы:

  • функция DivNumbers(), которая возвращает результат деления двух чисел, введенных из клавиатуры. В функции генерируется исключение типа int, если значение делителя равно 0;
  • функция SqRoot(), возвращающая корень из отрицательного числа. В функции генерируется исключение типа const char*, если значение параметра number отрицательно;
  • функция ProcessException(). Эта функция демонстрирует работу функций DivNumbers() и SqRoot(). В функции используется инструкция try…catch().
#include <iostream> 
using namespace std; 

// Пример. Демонстрация использования блока catch
// Функция, которая делит 2 числа и возвращает результат
double DivNumbers(double a, double b)
{
  if (b == 0) throw 1;
    return a / b;
}

// Функция, возвращающая корень из отрицательного числа
double SqRoot(double number)
{
  if (number < 0) throw "Negative number";
    return sqrt(number);
}

// Демонстрация блока catch(...)
void ProcessException()
{
  double v;

  // цикл отображения и вызова нужной функции
  while (1)
  {
    cout << "Input a function to call (1-2, 3-exit): " << endl;
    cout << "1-DivNumbers. 2-SqRoot" << endl;
    cout << ">>";
    cin >> v;

    // Вызвать различные варианты функций
    try {
      if (v == 1) // функция DivNumbers
      {
        double a, b;
        cout << "DivNumbers(double a, double b)" << endl;

        // ввести a, b
        cout << "a = "; cin >> a;
        cout << "b = "; cin >> b;

        // Вызвать функцию DivNumbers()
        double c = DivNumbers(a, b);
        cout << "c = " << c << endl;
      }
      if (v == 2)
      {
        double x, num;
        cout << "SqRoot(double num)" << endl;
        cout << "num = "; cin >> num;

        // Вызвать функцию SqRoot()
        x = SqRoot(num);
        cout << "x = " << x << endl;
      }
      if (v == 3) break;
    }
    catch (const char* e)
    {
      cout << "Error. Text = " << e << endl;
    }
    catch (...) // все другие типы исключений
    {
      cout << "Error in block catch(...)." << endl;
    }
  }
}

void main()
{
  ProcessException();
}

Как видно из текста функции ProcessException() вызов функций DivNumbers() и SqRoot() взят в блок try…catch

// Вызвать разные варианты функций
try {
  ...
}
catch (const char* e)
{
  cout << "Error. Text = " << e << endl;
}
catch (...) // все другие типы исключений
{
  cout << "Error in block catch(...)." << endl;
}

В блоке try…catch обрабатываются

  • исключение типа const char*;
  • все другие типы исключений. В этом случае используется инструкция catch(…).

Результат работы программы

Input a function to call (1-2, 3-exit):
1-DivNumbers. 2-SqRoot
>>2
SqRoot(double num)
num = -4
Error. Text = Negative number
Input a function to call (1-2, 3-exit):
1-DivNumbers. 2-SqRoot
>>1
DivNumbers(double a, double b)
a = 3
b = 0
Error in block catch(...).
Input a function to call (1-2, 3-exit):
1-DivNumbers. 2-SqRoot
>>1
DivNumbers(double a, double b)
a = 2
b = 5
c = 0.4
Input a function to call (1-2, 3-exit):
1-DivNumbers. 2-SqRoot
>>3

 


Связанные темы

  • Пример создания иерархии классов для обработки исключительных ситуаций

 


Понравилась статья? Поделить с друзьями:
  • Three past half где ошибка
  • Thread 1 signal sigterm ошибка
  • Thermona 14 tcl коды ошибок
  • This war of mine ошибка msvcp100 dll
  • This stream is already in use ошибка торрент