Exception c что это
Перейти к содержимому

Exception c что это

  • автор:

Exception c что это

Базовым для всех типов исключений является тип Exception . Этот тип определяет ряд свойств, с помощью которых можно получить информацию об исключении.

InnerException : хранит информацию об исключении, которое послужило причиной текущего исключения

Message : хранит сообщение об исключении, текст ошибки

Source : хранит имя объекта или сборки, которое вызвало исключение

StackTrace : возвращает строковое представление стека вызывов, которые привели к возникновению исключения

TargetSite : возвращает метод, в котором и было вызвано исключение

Например, обработаем исключения типа Exception:

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

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

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

DivideByZeroException : представляет исключение, которое генерируется при делении на ноль

ArgumentOutOfRangeException : генерируется, если значение аргумента находится вне диапазона допустимых значений

ArgumentException : генерируется, если в метод для параметра передается некорректное значение

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

InvalidCastException : генерируется при попытке произвести недопустимые преобразования типов

NullReferenceException : генерируется при попытке обращения к объекту, который равен null (то есть по сути неопределен)

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

В данном случае блоки catch обрабатывают исключения типов IndexOutOfRangeException и DivideByZeroException. Когда в блоке try возникнет исключение, то CLR будет искать нужный блок catch для обработки исключения. Так, в данном случае на строке

происходит обращение к 7-му элементу массива. Однако поскольку в массиве только 4 элемента, то мы получим исключение типа IndexOutOfRangeException. CLR найдет блок catch, который обрабатывает данное исключение, и передаст ему управление.

Следует отметить, что в данном случае в блоке try есть ситуация для генерации второго исключения — деление на ноль. Однако поскольку после генерации IndexOutOfRangeException управление переходит в соответствующий блок catch, то деление на ноль int y = x / 0 в принципе не будет выполняться, поэтому исключение типа DivideByZeroException никогда не будет сгенерировано.

Однако рассмотрим другую ситуацию:

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

Мы также можем определить для InvalidCastException свой блок catch, однако суть в том, что теоретически в коде могут быть сгенерированы самые разные типы исключений. А определять для всех типов исключений блоки catch, если обработка исключений однотипна, не имеет смысла. И в этом случае мы можем определить блок catch для базового типа Exception:

И в данном случае блок catch (Exception ex)<> будет обрабатывать все исключения кроме DivideByZeroException и IndexOutOfRangeException. При этом блоки catch для более общих, более базовых исключений следует помещать в конце — после блоков catch для более конкретный, специализированных типов. Так как CLR выбирает для обработки исключения первый блок catch, который соответствует типу сгенерированного исключения. Поэтому в данном случае сначала обрабатывается исключение DivideByZeroException и IndexOutOfRangeException, и только потом Exception (так как DivideByZeroException и IndexOutOfRangeException наследуется от класса Exception).

Exceptions

Exception handling provides a way of transferring control and information from some point in the execution of a program to a handler associated with a point previously passed by the execution (in other words, exception handling transfers control up the call stack).

An exception can be thrown by a throw-expression, dynamic_cast , typeid , new-expression, allocation function, and any of the standard library functions that are specified to throw exceptions to signal certain error conditions (e.g. std::vector::at , std::string::substr , etc).

In order for an exception to be caught, the throw-expression has to be inside a try-block or inside a function called from a try-block, and there has to be a catch clause that matches the type of the exception object.

When declaring a function, the following specification(s) may be provided to limit the types of the exceptions a function may throw:

  • dynamic exception specifications
  • noexcept specifications

Errors that arise during exception handling are handled by std::terminate and std::unexpected (until C++17) .

Contents

[edit] Usage

While throw-expression can be used to transfer control to an arbitrary block of code up the execution stack, for arbitrary reasons (similar to std::longjmp ), its intended usage is error handling.

[edit] Error handling

Throwing an exception is used to signal errors from functions, where «errors» are typically limited to only the following [1] [2] [3] :

  1. Failures to meet the postconditions, such as failing to produce a valid return value object.
  2. Failures to meet the preconditions of another function that must be called.
  3. (for non-private member functions) Failures to (re)establish a class invariant.

In particular, this implies that the failures of constructors (see also RAII) and most operators should be reported by throwing exceptions.

In addition, so-called wide contract functions use exceptions to indicate unacceptable inputs, for example, std::string::at has no preconditions, but throws an exception to indicate index out of range.

[edit] Exception safety

After the error condition is reported by a function, additional guarantees may be provided with regards to the state of the program. The following four levels of exception guarantee are generally recognized [4] [5] [6] , which are strict supersets of each other:

  1. Nothrow (or nofail) exception guarantee — the function never throws exceptions. Nothrow (errors are reported by other means or concealed) is expected of destructors and other functions that may be called during stack unwinding. The destructors are noexcept by default. (since C++11) Nofail (the function always succeeds) is expected of swaps, move constructors, and other functions used by those that provide strong exception guarantee.
  2. Strong exception guarantee — If the function throws an exception, the state of the program is rolled back to the state just before the function call (for example, std::vector::push_back ).
  3. Basic exception guarantee — If the function throws an exception, the program is in a valid state. No resources are leaked, and all objects’ invariants are intact.
  4. No exception guarantee — If the function throws an exception, the program may not be in a valid state: resource leaks, memory corruption, or other invariant-destroying errors may have occurred.

Generic components may, in addition, offer exception-neutral guarantee: if an exception is thrown from a template parameter (e.g. from the Compare function object of std::sort or from the constructor of T in std::make_shared ), it is propagated, unchanged, to the caller.

[edit] Exception objects

While objects of any complete type and cv pointers to void may be thrown as exception objects, all standard library functions throw anonymous temporary objects by value, and the types of those objects are derived (directly or indirectly) from std::exception . User-defined exceptions usually follow this pattern. [7] [8] [9]

To avoid unnecessary copying of the exception object and object slicing, the best practice for catch clauses is to catch by reference. [10] [11] [12] [13]

С++ exception handling под капотом или как же работают исключения в C++

В мире победили языки высокого уровня и в мирах руби-питон-js разработчиков остается только разглагольствовать, что в плюсах не стоит использовать то или иное. Например, исключения, потому что они медленные и генерируют много лишнего кода. Стоило спросить «и какой же код он генерирует», как в ответ получил мямленье и мычание. А и правда — как же они работают? Ну что ж, компилируем в g++ с флагом -S, смотрим что получилось. Поверхностно разобраться не сложно, однако то, что остались недопонимания — не давали мне спать. К счастью, готовая статья нашлась.

На хабре есть несколько статей, подробных и не очень (при этом все равно хороших), посвященных тому, как работают exceptions в C++. Однако нет ни одной по-настоящему глубокой, поэтому я решил восполнить этот пробел, благо есть подходящий материал. Кому интересно как работают исключения в C++ на примере gcc — запаситесь pocket-ом или evernote, свободным временем и добро пожаловать под кат.

2 часть
3 часть

P.S. Пару слов о переводе:

  • перевод очень очень близкий к тексту, но иногда я позволял себе изменять целые абзацы
  • некоторые термины я так и не придумал, как перевести, например landing pad и call site
  • Работы оказалось гораздо больше, чем казалось, к концу я даже стал путать, где перевод, а где оригинал, некоторые строки были написаны в 4 ночи, в общем — если где-то будут несвязные слова или целые предложения — простите, в ближайшее время постараюсь все подправить.
  • В данном случае код является неотъемлемой частью статьи, поэтому прятать под спойлер ничего не буду.
  • Как всегда, орфография, пунктуация и мелкие ошибки — в личку. Фактические ошибки, неточности и недоработки — в комментарии.

C++ исключения под капотом

Все знают, что обработка исключений трудна. Причин для этого предостаточно в каждом слое «жизненного цикла» исключения: сложно писать код с сильной гарантией безопасности по исключениям (exception safe code), исключения могут выбрасываться из неожиданных мест, может оказаться проблематичным попытка понять плохо спроектированную иерархию исключений, это медленно работает из-за большого количества вуду-магии под капотом, это опасно, поскольку неправильное пробрасывание ошибки может привести к непростительному вызову std::terminate . И, несмотря на все это, битвы по поводу использовать или нет исключения в программах все еще продолжаются. Вероятно, это из-за неглубокого понимания как же они работают.

Для начала нужно спросить себя: как это все работает? Это первая статья из длинной серии, которую я пишу о том, как реализованы исключения под капотом в C++ (под платформу gcc под x86, но должно быть применимо для других платформ так же). В этих статьях процес выброса и отлова ошибок будет объяснен во всех подробностях, но для нетерпеливых: короткий бриф всех статей о пробросе исключений в gcc/x86:

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

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

Для изучения всех деталей, происходящих под капотом, в следующей части мы начнем с реализации собственной мини-версии libstdlibc++ . Не всей, только части с обработкой ошибок. В реальности даже не всю эту часть, лишь необходимый минимум для реализации throw/catch блока. Также понадобится немного ассемблера, но лишь совсем совсем немного. Зато понадобится много терпения, к сожалению.

Если вы слишком любопытны, можете начинать тут. Это — полная спецификация того, что мы будем реализовывать в следующих частях. Я же попытаюсь сделать эту статью поучительной и более простой, чтобы в следующий раз вам было проще начинать с вашим собственным ABI (application binary interface, Двоичный интерфейс приложений — прим. переводчика).

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

Прим. переводчика: это актуально и для перевода.

C++ exceptions под капотом: маленький ABI

Если мы попытаемся понять, почему исключения такие сложные и как они работают, мы можем либо утонуть в тоннах мануалов и документаций, либо попытаться отловить исключения самостоятельно. В действительности, я был удивлен отстутствием качественной информации по теме (прим. переводчика — я, к слову, тоже): все, что можно найти либо чересчур детально, либо слишком уж простое. Конечно же, есть спецификации (наиболее документировано: ABI for C++, но так же CFI, DWARF и libstdc), но обособленного чтение документации недостаточно, если вы действительно хотите понять, что происходит внутри.

Давайте начнем с очевидного: с переизобретения колеса! Мы знаем, что в чистом C нет исключений, так что попытаемся слинковать C++ программу линкером чистого C и посмотрим, что произойдет! Я начал с чего-то простого типа этого:

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

И очень простой main:

Что случится, если мы попытаемся скомпилировать и слинковать этот франкинкод?

Заметка: вы можете загрузить весь исходный код для этого проекта с моего гит-репозитория.

Пока что все хорошо. Оба, g++ и gcc, счастливы в своем маленьком мире. Хаос начнется сразу, как только мы попробуем их слинковать вместе:

Ну и конечно же, gcc жалуется на недостающие C++ объявления. Это очень специфичные C++ объявления. Посмотрите на последнюю строку ошибки: пропущена vtable для cxxabiv1 . cxxabi , объявленная в libstdc++ , ссылается на ABI для C++. Теперь мы знаем, что обработка ошибок выполняется с помощью стандартной C++ библиотеки с объявленным интерфейсом C++ ABI.

C++ ABI объявляет стандартный бинарный формат, с помощью которого мы можем слинковать объекты вместе в одной программе. Если мы скомпилируем .o файлы двумя разными компиляторами, которые используют разные ABI, то не сможем объединить их в одно приложение. ABI может так же объявлять разные другие стандарты, например, интерфейс для раскручивания стека или пробрасывания исключения. В этом случае ABI определяет интерфейс (не обязательно бинарный формат, просто интерфейс) между C++ и другими библиотеками в нашем приложении, которые обеспечивают раскрутку стэка. Иными словами — ABI определяет специфичные для C++ вещи, благодаря которым наше приложение может общаться с не-C++ библиотеками: это то, что позволит пробрасывать исключения из других языков, которые будут отловлены в C++, ну и множество других вещей.

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

C++ exceptions под капотом: угождаем линкеру, подпихнув ему ABI

В нашем путешествии в понимании исключений мы открыли, что вся тяжелая атлетика реализована в libstdc++ , определение которой дано в C++ ABI. Просматривая ошибки линкера мы вывели, что для обработки ошибок мы должны обратиться за помощью к C++ ABI; мы создали плюющуюся ошибками C++ программу, слинковали вместе с программой на чистом C и обнаружили, что компилятор каким-то образом транслирует наши throw инструкции во что-то, что теперь вызывает несколько libstd++ функций, которые непосредственно выбрасывают исключение.

Тем не менее, мы хотим понять как именно работают исключения, так что попробуем реализовать свой собственный mini-ABI, обеспечивающий механизм пробрасывания ошибок. Чтобы сделать это, нам понадобится лишь RTFM, однако полный интерфейс может быть найден тут, для LLVM. Вспомним-ка, каких конкретно функций недостает:

__cxa_allocate_exception

Имя самодостаточно, я полагаю. __cxa_allocate_exception принимает size_t и выделяет достаточное количество памяти для хранения исключения во время его пробрасывания. Это сложнее, чем кажется: когда ошибка обрабатывается, происходит некая магия со стеком, аллоцирование (прим. переводчика — да простите за это слово, но иногда я буду его использовать) в стеке — плохая идея. Выделение памяти в куче (heap), в общем, тоже плохая идея, потому что где будем выделять память при исключении, сигнализирующем о том, что память закончилась? Статичное (static) размещение в памяти так же плохая идея, покуда нам нужно сделать это потокобезопасным (иначе два конкурирующих потока, выбросившие исключения, приведут к катастрофе). Учитывая эти проблемы, наиболее выгодным выглядит выделение памяти в локальном хранилище потока (куче), однако при необходимости обращаться к аварийному хранилищу (предположительно, статичному), если память закончилась (out of memory). Мы, конечно же, не будем волноваться по поводу страшных деталей, так что можем просто использовать статичный буфер, если понадобится.

__cxa_throw

Эта функция делает всю магию пробрасывания! Согласно ABI, как только исключение было создано, должна вызываться __cxa_throw. Эта функция ответственна за вызов раскрутки стэка. Важный эффект: __cxa_throw никогда не предполагает возврат (return). Она так же передает управление подходящему catch-блок для обработки исключения либо вызывает (по-умолчанию) std::terminate , но никогда ничего не возвращает.

vtable для __cxxabiv1::__class_type_info

Странно… __class_type_info явно какая-то RTTI (run-time type information, run-time type identification, Динамическая идентификация типа данных), но какая именно? Пока нам не просто ответить на это, да и это не адски важно для нашего мини-ABI; оставим это части «приложение», которую мы приведем после завершения анализа процесса пробрасывания исключения, сейчас же давайте просто скажем, что это — точка входа определения ABI в рантайме, отвечающая на вопрос: «эти два типа одинаковы или нет». Это функция, которая вызывается, чтобы определить: может ли данный catch-блок обрабатывать эту ошибку или нет. Сейчас мы сфокусируемся на основном: нам необходимо дать её как адрес для линкера (т.е. определить её не достаточно, нужно еще её инициировать) и она должна иметь vtable (да да, она должна иметь виртуальный метод).

Много работы происходит в этих функциях, но давайте попробуем реализовать простейший метатель исключений: тот, который будет делать выход из программы (call exit), когда исключение выброшено. Наше приложение почти завершено, но пропущены некоторые ABI-функции, так что давайте создадим mycppabi.cpp. Читая нашу ABI-спецификацию, мы можем описать наши сигнатуры для __cxa_allocate_exception и __cxa_throw:

Напомню: вы можете найти исходники в моем github репозитории.

Если сейчас скомпилировать mycppabi.cpp и слинковать с другими двумя .o файлами, мы получим работающие бинарники, которые должны вывести «alloc ex 1\n throw» и, после этого, выйти. Очень просто, но, при этом, удивительно: мы управляем исключениями без вызова libc++: мы написали (очень очень маленькую) часть C++ ABI!

Другая важная часть мудрости, полученная нами при создании нашего собственного мини-ABI: ключевое слово throw компилируется в два вызова функций из libstdc++. Здесь нет никакой вуду-магии, это простая трансформация. Мы можем даже дизассемблировать нашу функцию чтобы проверить это. Запустим g++ -S throw.cpp

Даже больше магии: когда throw транслируется в эти два вызова, компилятор даже не знает, как исключение будет обрабатываться. Как только libstdc++ определяет __cxa_throw и её друзей, libstdc++ динамически линкуется в рантайме, метод обработки исключений может быть выбран при первом запуске приложения.

Мы уже видим прогресс, но нам еще стоит пройти огромный путь познания. Сейчас наш ABI может только выбрасывать исключения. Можем ли мы расширить его, чтобы он отлавливал ошибки? Что ж, посмотрим, как это сделать, в следующей главе!

C++ exceptions под капотом: отлов того, что бросаем

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

  • throw-объявление будет транслировано компилятором в два вызова: __cxa_allocate_exception и __cxa_throw.
  • __cxa_allocate_exception и __cxa_throw «живут» в libstdc++ .
  • __cxa_allocate_exception выделяет память для нового исключения.
  • __cxa_throw выполняет подготовку и отдает исключение в _Unwind, в набор функций, которые живут в libstdc и производит реальное разворачивание стэка (ABI определяет интерфейс этих функций).

Как и ранее, мы имеем функцию seppuku, соединяющую C и C++ миры, только в этот раз мы добавили несколько вызовов функций, чтобы сделать наш стэк более интересным, также мы добавили ветви try/catch блоков, так что теперь мы можем анализировать как libstdc++ обрабатывает их.

И вновь получаем ошибки линковщика об отсутствующих ABI-функциях:

Мы опять видим кучу всего интересного. Вызов __cxa_begin_catch и __cxa_end_catch мы ожидали, хоть пока и не знаем, что они такое, но можем предположить, что они эквивалентны throw/__cxa_allocate/throw. __gxx_personality_v0 — что-то новое, и оно и будет основной темой следующих частей.

Что делает персональная функция? (при. переводчика — не придумал лучшего названия, подскажите в комментариях, если есть идеи). Мы уже что-то говорили о ней во введении, однако в следующий раз мы посмотрим на нее гораздо детальнее, как и на наших двух новых друзей: __cxa_begin_catch и __cxa_end_catch.

C++ exceptions под капотом: магия вокруг __cxa_begin_catch и __cxa_end_catch

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

Напомню, что код вы можете получить на моем гит-репозитории.

В теории (в нашей теории, разумеется), catch-блок транслируется в пару __cxa_begin_catch/end_catch из libstdc++, но и во что-то новое, называемое персональной функцией, о который мы пока еще ничего не знаем.

Давайте проверим нашу теорию о __cxa_begin_catch и __cxa_end_catch. Скомпилируем throw.cpp с флагом -S и проанализируем код ассемблера. Там есть много чего интересного, урежем до самого необходимого:

Все идет замечательно: мы получили такое же определение для raise(), лишь выброс исключения:

Определение для try_but_dont_catch() обрезано компилятором. Это что-то новое: ссылка на __gxx_personality_v0 и что-то другое, называемое LSDA. Это выглядит незначительным определением, однако в действительности это очень важно:

  • линкер использует это для спецификации CFI (call frame information); CFI хранит информацию о фрейме вызова, вот его полная спецификация. Он используется, в основном, для раскручивания стэка.
  • LDSA (language specific data area) — специальная область для каждого языка, используемая персональной функцией, чтобы знать, какие исключения могут быть обработаны данной функцией.

Еще одна элементарщина: просто вызываем raise и после этого прыгаем на L8; L8 делает обычный возврат из функции. Если raise выполнится неправильно, тогда выполнение (как-то, мы пока еще не знаем, как!) должно не продолжаться на следующей инструкции, а перейти к обработчику исключению (который в терминах ABI называется landing pads , об этом позже).

На первый взгляд этот кусок немного сложен, однако в действительности все просто. Наибольшее количество магии происходит тут: сначала мы проверяем — можем ли мы обрабатывать это исключение, если нет — вызываем _Unwind_Resume , если можем — вызываем __cxa_begin_catch и __cxa_end_catch , после этого функция должна продолжиться нормально и, таким образом, L8 будет выполнено (L8 прямо под нашим catch-блоком):

Лишь обычный возврат функции… с некоторым мусором CFI в нем.

Это все для обработки ошибок, тем не менее, мы до сих пор не знаем, как работают __cxa_begin/end_catch; у нас есть идеи как эта пара формирует то, что называет landing pad — место в функции, где располагаются обраотчики исключений. Что мы пока не знаем — как landing pads ищутся. Unwind должен как-то пройти все вызовы в стеке, проверить: имеет ли какой-либо вызов (фрейм стека для точности) валидный блок с landing pad, который может обрабатывать это исключение, и продолжить выполнение в нем.

Это немаловажное достижение, и как это работает мы выясним в следующей главе.

C++ exceptions под капотом: gcc_except_table и персональная функция

Ранее мы выяснили, что throw транслируется в пару __cxa_allocate_exception/throw, а catch-блок транслируется в __cxa_begin/end_catch, а также во что-то, именуемое CFI (call frame information) для поиска landing pads — точки входа обработчиков ошибок.

Что мы не знаем до сих пор, это как _Unwind узнает, где этот landing pads. Когда исключение пробрасывается сквозь связку функций в стэке, все CFI позволяют программе разворачивания стэка узнать, что за функция сейчас исполняется, а так же это необходимо, чтобы узнать, какой из landing pads функции позволяет нам обрабатывать данное исключение (и, к слову, мы игнорируем функции с множественными try/catch блоками!).

Чтобы выяснить, где же этот landing pads находится, используется что-то, зовущее себя gcc_except_table. Таблица эта может быть найдена (с мусором CFI) после конца функции:

Эта секция .gcc_except_table — где хранится вся информация для обнаружения landing pads, мы поговорим об этом позже, когда будем анализировать персональную функцию. Пока что мы лишь скажем, что LSDA означает — зона с специфичными для языка данными, которые персональная функция проверяет на наличие landing pads для функции (она также используется для запуска деструкторов в процессе разворачивания стэка).

Подытожим: для каждой функции, где есть по крайней мере один catch-блок, компилятор транслирует его в пару вызовов cxa_begin_catch/cxa_end_catch и, затем, персональная функция, вызываемая __cxa_throw, читает gcc_except_table для каждого метода в стэке для поиска чего-то, называемого LSDA. Персональная функция затем проверяет, есть ли в LSDA блок, обрабатывающий данное исключение, а так же есть ли какой-то код очистки (который запускает деструкторы когда нужно).

Еще мы можем сделать интересный вывод: если мы используем nothrow (или пустой оператор throw), компилятор может опустить gcc_except_table для метода. Этот способ реализации исключений в gcc, не сильно влияющий на производительность, в действительности сильно влияет на размер кода. Что касается catch-блоков? Если исключение пробрасывается, когда объявлен спецификатор nothrow, LSDA не генерируется и персональная функция не знает, что ей делать. Когда персональная функция не знает, что ей делать, она вызывает обработчик ошибок по-умолчанию, что, в большинстве случаев, означает, что выброс ошибки из nothrow метода закончится std::terminate.

Теперь, когда у нас есть идеи, что делает персональная функция, сможем ли мы реализовать её? Что ж, посмотрим!

Exception Handling in C++

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.

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *