Современные рекомендации по C по исключению и обработке ошибок

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

к содержанию ↑

Использование исключений для исключительного кода

Ошибки программы часто делятся на две категории:

  • Ошибки логики, вызванные ошибками программирования. Например, ошибка «индекс вне диапазона».
  • Ошибки среды выполнения, которые выходят за рамки контроля программиста. Например, ошибка «сетевая служба недоступна».

В программировании на языке C и com отчеты об ошибках управляются либо путем возврата значения, представляющего код ошибки или код состояния для конкретной функции, либо путем задания глобальной переменной, которую вызывающий объект может при необходимости получить после каждого вызова функции, чтобы узнать, были ли сообщения об ошибках. Например, com-программирование использует HRESULT возвращаемое значение для передачи ошибок вызывающей объекту. А API Win32 имеет GetLastError функцию для получения последней ошибки, сообщаемой стеком вызовов.

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

Исключения предпочтительны в современном C++ по следующим причинам:

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

В следующем упрощенном примере показан необходимый синтаксис для создания и перехвата исключений в C++:

#include #include #include using namespace std; void MyFunc(int c) < if (c >numeric_limits < char>::max()) < throw invalid_argument("MyFunc argument too large."); >//. > int main() < try < MyFunc(256); //cause an exception to throw >catch (invalid_argument& e) < cerr //. return 0; >

Исключения в C++ похожи на такие языки, как C# и Java. В блоке try , если исключение создается, он перехватывается первым связанным catch блоком, тип которого соответствует типу исключения. Другими словами, выполнение переходит от инструкции throw к оператору catch .

Если не найден доступный блок перехвата, std::terminate вызывается и программа завершает работу. В C++любой тип может быть создан; однако рекомендуется создать тип, производный напрямую или косвенно от std::exception . В предыдущем примере тип invalid_argument исключения определен в стандартной библиотеке в файле заголовка .

Новости по теме:   Статья 51. Порядок применения к сотрудникам органов внутренних дел мер поощрения и порядок наложения на них дисциплинарных взысканий

C++ не предоставляет или не требует finally блока, чтобы убедиться, что все ресурсы освобождены, если исключение возникает. Приобретение ресурсов — идиома инициализация (RAII), которая использует интеллектуальные указатели, предоставляет необходимые функциональные возможности для очистки ресурсов. Дополнительные сведения см.

в разделе «Практическое руководство . Проектирование для обеспечения безопасности исключений». Сведения о механизме очистки стека C++ см.

в разделе «Исключения» и очистка стека.

к содержанию ↑

Исключения и производительность

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

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

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

к содержанию ↑

Исключения и утверждения

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

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

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

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

к содержанию ↑

Исключения C++ и исключения SEH Для Windows

Программы C и C++ могут использовать структурированный механизм обработки исключений (SEH) в операционной системе Windows. Основные понятия в SEH похожи на те, которые используются в исключениях C++, за исключением того, что SEH использует __try __except __finally и конструкции, а не . try catch В компиляторе Microsoft C++ (MSVC) исключения C++ реализуются для SEH.

Однако при написании кода C++ используйте синтаксис исключения C++.

к содержанию ↑

Спецификации исключений и noexcept

Спецификации исключений были представлены в C++ в качестве способа указания исключений, которые может вызывать функция. Однако спецификации исключений оказались проблематичными на практике и устарели в стандарте черновика C++11. Рекомендуется не использовать throw спецификации исключений, за исключением того throw() , что функция не позволяет экранировать исключения.

Если необходимо использовать спецификации исключений устаревшей формы throw( type-name ) , поддержка MSVC ограничена. Дополнительные сведения см. в разделе «Спецификации исключений (throw)».

Описатель noexcept представлен в C++11 в качестве предпочтительной альтернативы throw() .

к содержанию ↑

Простые примеры

Рассмотрим два простых примера, на основе которых можно описать большинство происходящих ошибок:

Point readPoint()

Points readPoints()

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

Point point = readPoint(); if (point != null)

Points points = readPoints(); if (points != null)

Новости по теме:   Система арбитражных судов

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

Если функция возвращает null, то она не выполняет свои обязанности. Из сигнатуры функции понятно, что она возвращает точку или набор точек, но null — это не точка, поэтому функция нас обманывает.

В некоторых ситуациях разумно возвращать специальное значение. Точка с нулевыми или отрицательными координатами мало подходит для таких задач. Система может быть расширена, и тогда точка попадет во множество нормальных точек, код начнет вести себя непредсказуемо.

Можно придумать специальную точку.

А вот вернуть пустой набор точек во второй функции вполне разумно в некоторых случаях:

Points points = readPoints(); for (Point point : points)

Цикл просто не выполнит ни одной итерации, функция обладает ожидаемым поведением.

Использование исключений для исключительного кода

Ошибки программы часто делятся на две категории:

  • Ошибки логики, вызванные ошибками программирования. Например, ошибка «индекс вне диапазона».
  • Ошибки среды выполнения, которые выходят за рамки контроля программиста. Например, ошибка «сетевая служба недоступна».

В программировании на языке C и com отчеты об ошибках управляются либо путем возврата значения, представляющего код ошибки или код состояния для конкретной функции, либо путем задания глобальной переменной, которую вызывающий объект может при необходимости получить после каждого вызова функции, чтобы узнать, были ли сообщения об ошибках. Например, com-программирование использует HRESULT возвращаемое значение для передачи ошибок вызывающей объекту. А API Win32 имеет GetLastError функцию для получения последней ошибки, сообщаемой стеком вызовов.

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

Исключения предпочтительны в современном C++ по следующим причинам:

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

В следующем упрощенном примере показан необходимый синтаксис для создания и перехвата исключений в C++:

#include #include #include using namespace std; void MyFunc(int c) < if (c >numeric_limits < char>::max()) < throw invalid_argument("MyFunc argument too large."); >//. > int main() < try < MyFunc(256); //cause an exception to throw >catch (invalid_argument& e) < cerr //. return 0; >

Исключения в C++ похожи на такие языки, как C# и Java. В блоке try , если исключение создается, он перехватывается первым связанным catch блоком, тип которого соответствует типу исключения. Другими словами, выполнение переходит от инструкции throw к оператору catch .

Если не найден доступный блок перехвата, std::terminate вызывается и программа завершает работу. В C++любой тип может быть создан; однако рекомендуется создать тип, производный напрямую или косвенно от std::exception . В предыдущем примере тип invalid_argument исключения определен в стандартной библиотеке в файле заголовка .

C++ не предоставляет или не требует finally блока, чтобы убедиться, что все ресурсы освобождены, если исключение возникает. Приобретение ресурсов — идиома инициализация (RAII), которая использует интеллектуальные указатели, предоставляет необходимые функциональные возможности для очистки ресурсов. Дополнительные сведения см.

в разделе «Практическое руководство . Проектирование для обеспечения безопасности исключений». Сведения о механизме очистки стека C++ см.

в разделе «Исключения» и очистка стека.

к содержанию ↑

Производительность

Но эти затраты становятся актуальными только при возникновении ошибок. Что код делает 99,9% времени? Работает, а не обрабатывает ошибки.

Зачем снижать производительность своего кода, вынося код обработки ошибок в бизнес логику, делая его трудным для сопровождения и менее стабильным?

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

while (!queue.isEmpty()) < Point point = queue.readPoint(); if (point != null) < . >>

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

Пример с использованием исключений:

try < while (!queue.isEmpty()) < Point point = queue.readPoint(); . >> catch(PointNotFoudException ex)

Можно упростить код, разделив бизнес-логику и код обработки ошибок:

try < while (!queue.isEmpty()) < processNextPoint(); >> catch(PointNotFoudException ex) < throw new QueueReadingException(ex); >void processNextPoint() throws PointNotFoudException

Сколько раз в процессе штатной работы будет инициировано исключение QueueReadingException? В коде без ошибок — ни разу! Где же тут затраты?

к содержанию ↑

Поток данных из сети

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

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

При использовании исключений, инициация исключения не такая уж частая процедура.

Например, затраты на одно сравнение равны единице производительности. Инициация исключения 5 единиц. Пусть очередь содержит 100 точек, с 10% вероятностью возникновения ошибок чтения.

Получается, на сравнениях теряется 100 единиц производительности, на инициации исключений в худшем случае 50 единиц. Если же увеличивать количество точек в очереди, то разница будет только возрастать(для тысячи точек она равна уже 900 единиц).

к содержанию ↑

Что такое исключения в программировании

Рассказ об ошибках, которые можно предусмотреть заранее.

Большинство наших проектов устроены так: когда во время работы программы возникает какая-то ошибка, то программа аварийно завершается. Иногда при этом она выдаёт сообщение об ошибке. Кажется, что это нормальная ситуация, но на самом деле большинство ошибок можно предусмотреть и научить программу правильно с ними работать.

Для этого нам нужны обработчики ошибок.

к содержанию ↑

Что такое обработчик ошибок

Мнение эксперта
Митрофанов Денис Романович

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

  1. Мы заранее прикидываем, в каком месте и почему может возникнуть ошибка.
  2. Пишем в этом месте специальный код, который предупредит компьютер, что это плановая ошибка и что у нас уже есть решение, мол, всё под контролем.
  3. Компьютер применяет наше решение и переходит к следующей команде.
  4. Программа не падает, не завершается с ошибкой, а продолжает работать.

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

к содержанию ↑

Когда что-то не предусмотрено — будет ошибка

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

  • файл есть на диске, но к нему нет прав доступа;
  • файл занят другой программой;
  • сам диск повреждён и данные не читаются.

Во всех этих случаях программа сломается, потому что мы не предусмотрели эти ситуации:

Следующая
Судебные вопросыОбвинение в воровстве без доказательств

Добавить комментарий