Обработка исключений C++: пример Try, Catch, throw
Обработка исключений в C++ предоставляет вам способ обработки непредвиденных обстоятельств, таких как ошибки во время выполнения. Поэтому при возникновении непредвиденных обстоятельств управление программой передается специальным функциям, известным как обработчики.
Чтобы перехватить исключения, вы помещаете какой-то участок кода под проверку исключений. Часть кода помещается в блок try-catch.
Если в этом разделе кода возникает исключительная ситуация, будет выброшено исключение. Далее обработчик исключений возьмет на себя управление программой.
Если не произойдет никаких исключительных обстоятельств, код будет выполняться нормально. Обработчики будут игнорироваться.
В этом уроке C++ вы узнаете:
Почему обработка исключений?
Вот причина использования обработки исключений в C++:
- Вы отделите код обработки ошибок от обычного кода. Кода будет больше readable и проще в обслуживании.
- Функции могут обрабатывать выбранные ими исключения. Даже если функция выдает много исключений, она обработает только некоторые из них. Вызывающий объект будет обрабатывать неперехваченные исключения.
Ключевые слова обработки исключений
Обработка исключений в C++ вращается вокруг этих трех ключевых слов:
- бросать– когда программа сталкивается с проблемой, она выдает исключение. Ключевое слово throw помогает программе выполнить бросок.
- поймать– программа использует обработчик исключений для перехвата исключения. Он добавляется в раздел программы, где нужно разобраться с проблемой. Это делается с помощью ключевого слова catch.
- стараться– блок try идентифицирует блок кода, для которого будут активированы определенные исключения. За ним должен следовать один или несколько блоков catch.
Предположим, блок кода вызовет исключение. Исключение будет перехвачено методом с использованием ключевых слов try и catch. Блок try/catch должен окружать код, который может генерировать исключение. Такой код известен как защищенный код.
Синтаксис
Try/catch использует следующий синтаксис:
try < // the protected code >catch( Exception_Name exception1 ) < // catch block >catch( Exception_Name exception2 ) < // catch block >catch( Exception_Name exceptionN ) < // catch block >
- Хотя у нас есть один оператор try, у нас может быть много операторов catch.
- ExceptionName — это имя исключения, которое необходимо перехватить.
- «Exception1», «Exception2» и «ExceptionN» — это определенные вами имена для ссылки на исключения.
Пример 1:
#include #include using namespace std; int main() < vectorvec; vec.push_back(0); vec.push_back(1); // access the third element, which doesn't exist try < vec.at(2); >catch (exception& ex) < cout return 0; >
Вывод:
Вот скриншот кода:
Пояснение к коду:
- Включите заголовочный файл iostream в программу, чтобы использовать его. Функции.
- Включите векторный заголовочный файл в программу, чтобы использовать ее функции.
- Включите пространство имен std в программу в ее классы, не вызывая его.
- Вызовите функцию main(). Логика программы должна быть добавлена в ее тело.
- Создайте вектор с именем vec для хранения целочисленных данных.
- Добавьте элемент 0 в вектор с именем vec.
- Добавьте элемент 1 в вектор с именем vec.
- Комментарий. Он будет пропущен Компилятор C ++.
- Используйте оператор try, чтобы перехватить исключение. < отмечает начало тела блока try/catch. Код, добавленный в тело, станет защищенным кодом.
- Попробуйте получить доступ к элементу, хранящемуся по индексу 2 (третий элемент) вектора с именем vec. Этот элемент не существует.
- Конец тела блока try/catch.
- Поймать исключение. Возвращенное сообщение об ошибке будет сохранено в переменной ex.
- Выведите какое-нибудь сообщение на консоль, если исключение будет обнаружено.
- Конец корпуса блока улавливания.
- Программа должна вернуть значение после успешного выполнения.
- Конец тела функции main().
Пример 2:
#include using namespace std; double zeroDivision(int x, int y) < if (y == 0) < throw "Division by Zero!"; >return (x / y); > int main() < int a = 11; int b = 0; double c = 0; try < c = zeroDivision(a, b); cout catch (const char* message) < cerr return 0; >
Вывод:
Вот скриншот кода:
Пояснение к коду:
- Включите заголовочный файл iostream в программу, чтобы использовать ее функции.
- Включите пространство имен std в программу в ее классы, не вызывая его.
- Создайте функцию с именем ZeroDivision, которая принимает два целочисленных аргумента: x и y. Функция должна возвращать double результат.
- Используйте оператор if, чтобы проверить, равно ли значение аргумента переменной y 0. < отмечает начало тела if.
- Сообщение, которое будет возвращено/выброшено, если y равен 0.
- Конец тела оператора if.
- Функция ZeroDivision должна возвращать значение x/y.
- Конец тела функции ZeroDivision.
- Вызовите метод main(). < отмечает начало этого метода.
- Объявите целочисленную переменную и присвойте ей значение 11.
- Объявите целочисленную переменную b и присвойте ей значение 0.
- Объявить double переменную c и присвоив ей значение 0.
- Используйте оператор try, чтобы перехватить исключение. < отмечает начало тела блока try/catch. Код, добавленный в тело, станет защищенным кодом.
- Вызов функции ZeroDivision и передача аргументам a и b, то есть 11 и 0. Результат этого operaция будет храниться в переменной c.
- Выведите значение переменной c на консоль.
- Конец тела блока try/catch.
- Поймать исключение. Возвращенное сообщение об ошибке будет сохранено в переменной message.
- Распечатайте возвращенное сообщение об ошибке на консоли.
- Конец корпуса блока улавливания.
- Программа должна вернуть значение после успешного выполнения.
- Конец тела функции main().
Стандартные исключения C++
C++ поставляется со списком стандартных исключений, определенных в сорт. Они описаны ниже:
Исключение | Описание |
---|---|
std :: exception | Это исключение и родительский класс для всех стандартных исключений C++. |
std :: bad_alloc | Это исключение генерируется новым ключевым словом. |
станд::bad_cast | Это исключение, создаваемое динамическим_cast. |
std::bad_Exception | Полезное устройство для обработки неожиданных исключений в программах на C++. |
std::bad_typeid | Исключение, выдаваемое typeid. |
std :: logic_error | Это исключение теоретически можно обнаружить путем чтения кода. |
std::domain_error | Это исключение, возникающее после использования математически неверного домена. |
std :: invalid_argument | Исключение, вызванное использованием недопустимых аргументов. |
std::length_error | Исключение, возникающее после создания большого std::string. |
std::out_of_range | Выброшено методом. |
std :: runtime_error | Это исключение, которое невозможно обнаружить путем чтения кода. |
std::overflow_error | Это исключение генерируется после возникновения математического переполнения. |
std::range_error | Это исключение генерируется при попытке сохранить значение, выходящее за пределы допустимого диапазона. |
std::underflow_error | Исключение, возникающее после возникновения математического опустошения. |
Пользовательские исключения
Класс C++ std::Exception позволяет нам определять объекты, которые могут быть исключены как исключения. Этот класс был определен в заголовок. Класс предоставляет нам виртуальную функцию-член с именем What.
Эта функция возвращает последовательность символов, завершающуюся нулем, типа char *. Мы можем перезаписать его в производных классах, чтобы получить описание исключения.
Пример:
#include #include using namespace std; class newException : public exception < virtual const char* what() const throw() < return "newException occurred"; >> newex; int main() < try < throw newex; >catch (exception& ex) < cout return 0; >
Вывод:
Вот скриншот кода:
Пояснение к коду:
- Включите заголовочный файл iostream в нашу программу. Мы будем использовать его функции, не получая ошибок.
- Включите файл заголовка исключения в нашу программу. Мы будем использовать его функции как то без ошибок.
- Включите пространство имен std в нашу программу, чтобы использовать его классы, не вызывая его.
- Создайте новый класс с именем newException. Этот класс наследует класс исключений C++.
- Начало тела класса.
- Перезапишите виртуальную функцию-член What(), определенную в файле заголовка исключения. Затем мы опишем наше собственное исключение, новое исключение.
- Начните определение нового исключения.
- Сообщение, которое будет возвращено, если будет обнаружено новое исключение.
- Конец определения нового исключения.
- Конец тела класса newException. Newex — это имя, которое будет использоваться для перехвата нашего нового исключения, после чего будет вызвано новое исключение.
- Вызовите функцию main(). Логика программы должна быть добавлена в ее тело. < отмечает начало тела.
- Используйте оператор try, чтобы отметить код, в котором нам нужно отметить исключение. < отмечает начало тела блока try/catch. Код, окруженный этим, станет защищенным.
- Выдайте исключение newex, если оно будет обнаружено.
- Конец тела попытки.
- Используйте оператор catch, чтобы перехватить исключение. Сообщение об ошибке исключения будет сохранено в переменной ex.
- Распечатайте сообщение об ошибке исключения на консоли.
- Конец тела оператора catch.
- Программа должна вернуть значение, если она выполняется успешно.
- Конец тела функции main().
Обзор
- Благодаря обработке исключений в C++ вы можете обрабатывать ошибки во время выполнения.
- Ошибки времени выполнения — это ошибки, возникающие во время выполнения программы.
- Обработка исключений помогает вам справиться с любыми непредвиденными обстоятельствами в вашей программе.
- При возникновении непредвиденного обстоятельства управление программой передается обработчикам.
- Чтобы перехватить исключение, вы помещаете часть кода под блок try-catch.
- Ключевое слово throw помогает программе генерировать исключения, помогая программе справиться с проблемой.
- Ключевое слово try помогает определить блок кода, для которого будут активированы определенные исключения.
- Мы можем перезаписать функцию What() файла заголовка исключения, чтобы определить наши исключения.
- std::list в C++ с примером
- Как загрузить и установить C++ IDE на Windows
- Программа Hello World на C++ с объяснением кода
- Переменные и типы C++: Int, Char, Float, Double, строка и логическое значение
- Учебник по C++ для начинающих: изучите основы программирования за 7 дней
- Разница между структурой и классом в C++
- Учебное пособие по C++ в формате PDF для начинающих (загрузить сейчас)
- Статическая функция-член в C++ (примеры)
Пишем try-catch в C не привлекая внимания санитаров
Всё началось с безобидного пролистывания GCC расширений для C. Мой глаз зацепился за вложенные функции. Оказывается, в C можно определять функции внутри функций:
int main() < void foo(int a) < printf("%d\n", a); >for(int i = 0; i
Более того, во вложенных функциях можно менять переменные из внешней функции и переходить по меткам из неё, но для этого необходимо, чтобы переменные были объявлены до вложенной функции, а метки явно указаны через __label__
int main() < __label__ end; int i = 1; void ret() < goto end; >void inc() < i ++; >while(1) < if(i >10) ret(); printf("%d\n", i); inc(); > end: printf("Done\n"); return 0; >
Документация говорит, что обе внутренние функции валидны, пока валидны все переменные и мы не вышли из области внешней функции, то есть эти внутренние функции можно передавать как callback-и.
Приступим к написанию try-catch. Определим вспомогательные типы данных:
// Данными, как и выкинутой ошибкой может быть что угодно typedef void *data_t; typedef void *err_t; // Определяем функцию для выкидывания ошибок typedef void (*throw_t)(err_t); // try и catch. Они тоже будут функциями typedef data_t (*try_t)(data_t, throw_t); typedef data_t (*catch_t)(data_t, err_t);
Подготовка завершена, напишем основную функцию. К сожалению на хабре нельзя выбрать отдельно язык C, поэтому будем писать try_ , catch_ , throw_ чтобы их подсвечивало как функции, а не как ключевые слова C++
data_t try_catch(try_t try_, catch_t catch_, data_t data) < __label__ fail; err_t err; // Объявляем функцию выбрасывания ошибки void throw_(err_t e) < err = e; goto fail; >// Передаём в try данные и callback для ошибки return try_(data, throw_); fail: // Если есть catch, передаём данные, над которыми // работал try и ошибку, которую он выбросил if(catch_ != NULL) return catch_(data, err); // Если нет catch, возвращаем пустой указатель return NULL; >
Напишем тестовую функцию взятия квадратного корня, с ошибкой в случае отрицательного числа
data_t try_sqrt(data_t ptr, throw_t throw_) < float *arg = (float *)ptr; if(*arg < 0) throw_("Error, negative number\n"); // Выделяем кусок памяти для результата float *res = malloc(sizeof(float)); *res = sqrt(*arg); return res; >data_t catch_sqrt(data_t ptr, err_t err) < // Если возникла ошибка, печатает её и ничего не возвращаем fputs(err, stderr); return NULL; >
Добавляем функцию main, посчитаем в ней корень от 1 и от -1
int main() < printf("------- sqrt(1) --------\n"); float a = 1; float *ptr = (float *) try_catch(try_sqrt, catch_sqrt, &a); if(ptr != NULL) < printf("Result of sqrt is: %f\n", *ptr); // Не забываем освободить выделенную память free(ptr); >else printf("An error occured\n"); printf("------- sqrt(-1) -------\n"); a = -1; ptr = (float *)try_catch(try_sqrt, catch_sqrt, &a); if(ptr != NULL) < printf("Result of sqrt is: %f\n", *ptr); // Аналогично free(ptr); >else printf("An error occured\n"); return 0; >
И, как и ожидалось, получаем
------- sqrt(1) -------- Result of sqrt is: 1.000000 ------- sqrt(-1) ------- Error, negative number An error occured
Try-catch готов, господа.
На этом статью можно было бы и закончить, но тут внимательный читатель заметит, что функция throw остаётся валидной в блоке catch . Можно вызвать её и там, и тогда мы уйдём в рекурсию. Заметим также, что функция throw , это не обычная функция, она noreturn и разворачивает стек, поэтому, даже если вызвать её в catch пару сотен раз, на стеке будет только последний вызов. Мы получаем хвостовую оптимизацию рекурсии.
Попробуем посчитать факториал на нашем try-catch. Для этого передадим указатель на функцию throw в функцию catch . Сделаем это через структуру, в которой также будет лежать аккумулятор вычислений.
struct args < uint64_t acc; throw_t throw_; >;
В функции try инициализируем поле throw у структуры, и заводим переменную num для текущего шага рекурсии.
data_t try_(data_t ptr, throw_t throw_) < struct args *args = ptr; // Записываем функцию в структуру, чтобы catch мог её pf,hfnm args->throw_ = throw_; // Заводим переменную для хранения текущего шага рекурсии uint64_t *num = malloc(sizeof(uint64_t)); // Изначально в acc лежит начальное число, в нашем случае 10 *num = args->acc; // Уменьшаем число (*num) --; // Уходим в рекурсию throw_(num); >
В функции catch будем принимать структуру и указатель на num, а дальше действуем как в обычном рекурсивном факториале.
data_t catch_(data_t ptr, err_t err) < struct args *args = ptr; // В err на самом деле лежит num uint64_t *num = err; // Печатаем num, будем отслеживать рекурсию printf("current_num: %"PRIu64"\n", *num); if(*num >0) < args->acc *= *num; (*num) --; // Рекурсивный вызов args->throw_(num); > // Конец рекурсии // Не забываем осовободить выделенную память free(num); // Выводим результат printf("acc is: %"PRIu64"\n", args->acc); return &args->acc; >
int main() < struct args args = < .acc = 10 >; try_catch(try_, catch_, &args); return 0; >
Вызываем, и получаем, как и ожидалось:
current_num: 9 current_num: 8 current_num: 7 current_num: 6 current_num: 5 current_num: 4 current_num: 3 current_num: 2 current_num: 1 current_num: 0 acc is: 3628800
#include #include #include #include #include typedef void *err_t; typedef void *data_t; typedef void (*throw_t)(err_t); typedef data_t (*try_t)(data_t, throw_t); typedef data_t (*catch_t)(data_t, err_t); data_t try_catch(try_t try, catch_t catch, data_t data) < __label__ fail; err_t err; void throw(err_t e) < err = e; goto fail; >return try(data, throw); fail: if(catch != NULL) return catch(data, err); return NULL; > struct args < uint64_t acc; throw_t throw_; >; data_t try_(data_t ptr, throw_t throw_) < struct args *args = ptr; args->throw_ = throw_; uint64_t *num = malloc(sizeof(uint64_t)); *num = args->acc; (*num) --; throw_(num); > data_t catch_(data_t args_ptr, err_t num_ptr) < struct args *args = args_ptr; uint64_t *num = num_ptr; printf("current_num: %"PRIu64"\n", *num); if(*num >0) < args->acc *= *num; (*num) --; args->throw_(num); > free(num); printf("acc is: %"PRIu64"\n", args->acc); return &args->acc; > int main() < struct args args = < .acc = 10 >; try_catch(try_, catch_, &args); return 0; >
Спасибо за внимание.
P.S. Текст попытался вычитать, но, так как русского в школе не было, могут быть ошибки. Прошу сильно не пинать и по возможности присылать всё в ЛС, постараюсь реагировать оперативно.
Что такое try catch в C#?
Исключения – это то, что может непредвиденно возникнуть в ходе работы программы. Если такое случиться, то программа просто перестанет работать или же вовсе выключиться. Чтобы такого не допустить вам всегда стоит добавлять исключения при работе с различными вещами. К примеру, вы хотите добавить возможность деления двух чисел? Добавьте также исключение, которое позволит проверять, не является ли делитель нулем. Вы хотите открыть файл для записи в него? Пропишите исключение, которое будет срабатывать если файл не найден, чтобы программа не пыталась его открывать повторно. Логика, я думаю, ясна.
Конструкция самих исключений очень проста. Вам необходимо сначала что-то попробовать сделать, а потом поискать ошибку, если она будет найдена, то выведется ваш код, иначе все сработает корректно и никаких ошибок не будет.
FileStream fs = null; try < // Открываем какой-либо файл fs = new FileStream(@"C: empdata.txt", FileMode.Open); StreamReader sr = new StreamReader(fs); string line; // Данные считываются из файла и выводятся в консоль line = sr.ReadLine(); Console.WriteLine(line); >catch(FileNotFoundException e) < // Если что-то пошло не так Console.WriteLine("Файл не найден!"); // Ниже сообщаем компилятору об конкретной ошибке, но программу не прекращаем throw new FileNotFoundException(@"[data.txt не в c: emp папке]", e); >finally < // Выполниться в любом случае if (fs != null) // Если файл открыт fs.Close(); // То закрываем его >
throw и try-catch — что это и зачем нужно?
из лекций в вузе поняла, что блок try-catch нужен для отлавливания исключений, но так ли это? И зачем тогда throw ? Почему нельзя использовать простую обработку исключений? Зачем они нужны, если можно спокойно найти ошибку дебаггером и использовать обработку исключений?
Отслеживать
задан 13 июн 2023 в 9:23
37 5 5 бронзовых знаков
«нужен для отлавливания исключений» Да. «зачем тогда throw» Он вызывает («бросает») исключение, которое можно поймать catch . Они работают в паре. «использовать обработку исключений?» Что вы под этим имеете в в виду? catch нужен чтобы программа не падала при исключении, а могла как-то его переварить и продолжить работать.
13 июн 2023 в 9:32
вообще хороший ответ есть тут -> ru.stackoverflow.com/questions/558472/…
13 июн 2023 в 9:34
Под «использовать обработку исключений?» я имела ввиду использование тех же if. К примеру когда идет вычисление 1/х, где х изменяется в цикле, то ведь можно обойтись простым if при х=0, и смысл этого блока отпадает. Можете, пожалуйста, привести пример, где этот блок необходим и его нельзя ничем заменить?
13 июн 2023 в 9:35
Простите, а как именно поступить при том же обнаружении деления на 0? Прекратить выполнение программы? Вернуть какое-то значение (и как его отличить от обычного возвращаемого значения)? Так вы планируете обработку в том месте, где возникла ситуация. И как какая-то библиотечная функция N-ой вложенности может знать, как обработать исключение правильно? А если возвращать какой-то признак ошибки — значит, все функции по стеку вызовов должны содержать свой обработчик ошибок. Можно, но дорого и неудобно.
13 июн 2023 в 9:40
Исключения не используются для ошибок кода в программе. Они используются для обработки ошибок во время выполнения программы, которые возникают при получении неподходящих данных или работы в неподходящем окружении (например программа пробует читать из файла, которого может не оказаться). Такие ошибки можно обрабатывать посредством нагромождения if и кодов возвратов (С-style), однако исключения могут работать эффективнее, как в плане скорости получаемого кода, так и в плане его упрощения.
13 июн 2023 в 9:44
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
Что-бы чутка въехать в разного рода исключения, можно рассмотреть к примеру такой код. Где разные условия исключений создают разное поведение программы.
При этом исключения в отличии от флагов, тебе позволяют их отлавливать на разных уровнях приложения. Не только внутри тела одной функции но и раздельно по классам. К примеру если отвалился SQL в базу, то всё в порядке просто грохаем сам запрос. А если отвалилось всё соединение с базой то это совсем другой разговор. Там надо принимать кардинальные меры и перезапускать соединение по новой.
// Online C++ compiler to run C++ program online #include #include class ThisWillStopOnlyLocal: public std::exception < public: char const * what() const noexcept override < return "Custom C++ Exception"; >>; class ThisWillStopAll: public std::exception < public: char const * what() const noexcept override < return "Custom C++ Exception"; >>; int main() < int input; try < try < std::cout > input; switch (input) < case 1: throw ThisWillStopOnlyLocal<>; case 2: throw ThisWillStopAll<>; > std::cout catch (ThisWillStopOnlyLocal const & e) < std::cout std::cout catch (ThisWillStopAll const & e) < std::cout return 0; >
Отслеживать
29.9k 3 3 золотых знака 17 17 серебряных знаков 36 36 бронзовых знаков
ответ дан 13 июн 2023 в 12:17
3,238 1 1 золотой знак 6 6 серебряных знаков 20 20 бронзовых знаков
Операторы throw и try используются для работы с исключениями. Используйте оператор throw , чтобы создать исключение. Используйте инструкцию для перехвата try и обработки исключений, которые могут возникнуть во время выполнения блока кода.
Когда возникает критическая или неожиданная ситуация, программа может сгенерировать исключение с помощью оператора throw . Это позволяет передать управление к месту, где исключение будет обработано.
Оператор throw принимает в качестве аргумента объект-исключение, который может быть любым типом, но обычно это класс, производный от базового класса std::exception . Объект-исключение содержит информацию о типе ошибки или необычной ситуации.
Исключение может быть перехвачено и обработано с помощью блока try-catch . Блок try содержит код, который может вызвать исключение, а блок catch содержит код, который обрабатывает исключение. Если в блоке try возникает исключение, выполнение кода в этом блоке прерывается, и выполнение переходит в соответствующий блок catch , соответствующий типу исключения.
В общем говоря, практика отлова ошибок позволяет повысить устойчивость программы от непредвиденных обстоятельств. Создание собственных ошибок — буквально вклад в будущее, читая огромный модуль, в котором есть ошибка, мы можем сразу понять в каком месте она была вызвана, чтобы начать анализировать конкретно это место.