C++.Бархатный путь



Работа системы управления исключением - часть 3


  • блок перехвата исключения соответствует возбуждённому исключению, если в их объявлении и генерации использован один и тот же тип;
  • если возбуждаемое исключение может быть преобразовано к типу исключения, объявленного в блоке перехвата путём неявного преобразования типа, исключение считается соответствующим данному блоку перехвата;
  • если возбуждаемое исключение преобразуется к типу исключения, объявленного в блоке перехвата путём явного преобразования типа, оно считается соответствующим данному блоку перехвата;
  • исключение, которое является объектом-представителем производного класса, соответствует блоку перехвата, в котором объявлено исключение-представитель базового класса. Таким образом, исключение производного класса может быть перехвачено в блоке перехвата, в котором объявлено исключение-представитель базового класса. Это обстоятельство следует учитывать при расположении в программе блоков, определяющих списки реакций. В списке реакций контролируемого блока операторов перехватчики исключений, порождённых базовыми классами, должны располагаться в списке исключений ниже перехватчиков исключений, представляющих производные классы;
  • блок перехвата, содержащий вместо объявления исключения многоточие catch (...) {/*...*/}, соответствует любому исключению. Это своего рода универсальный блок перехвата. Он должен завершать список перехватчиков, поскольку ни один блок перехвата после него не сможет быть выполнен для обработки данного исключения, поскольку все возможные исключения будут перехвачены этим блоком.
  • Как известно, конструкторы и деструкторы не возвращают значений. Но в них могут быть размещены операторы генерации исключений. Если теперь программный код, обеспечивающий вызов конструкторов или деструкторов разместить в try-операторе, то можно будет организовать перехват исключения от конструкторов и деструкторов. Возбуждение исключения в конструкторе должно сопровождаться, если это необходимо, автоматическим вызовом деструкторов для уничтожения образующих этот объект составных элементов (если таковые существуют). Если исключительная ситуация возникла в ходе создания массива объектов, вызываемый в результате генерации исключения деструктор уничтожит лишь созданные на момент возникновения исключительной ситуации объекты.

    Если соответствующий блок перехвата был обнаружен и содержит именованный параметр, временный объект, созданный throw операцией, его инициализирует. Здесь всё происходит примерно также, как и при вызове функции. Для инициализации параметра исключения, являющегося представителем какого-либо класса, может потребоваться собственная версия конструктора копирования и деструктора. Проинициализированный именованный параметр получает доступ к информации, заложенной в исключение в момент его генерации. И здесь уместна аналогия с вызовом функции. Существует проинициализированный и поименованный параметр - будет и доступ к передаваемой информации. В ряде случаев, как и при вызове функции, без конкретного значения параметра можно и обойтись - лишь бы вовремя активизировался соответствующий обработчик и принял бы соответствующие меры по ликвидации последствий исключительной ситуации. А меры в этой связи могут быть приняты самые разнообразные. Здесь всё определяется конкретной задачей.

    Стартовав из try-блока, в результате возникновения исключительной ситуации, при благоприятном стечении обстоятельств, мы оказались в одном из связанных с ним блоков перехвата исключения. По сигналу тревоги, благодаря системе программирования C++, в нужное время мы прибыли в нужное место. Теперь всё зависит от программиста. Наши действия в catch-блоке практически ничем не ограничены. Выведем ли мы предупредительное сообщение на экран, исправим ли значение индекса массива, запросим ли новое значение для делителя - это транслятор не волнует. Формально мы совершили действие, в результате которого исключительная ситуация перехвачена, а её причина, возможно, что и ликвидирована. Что бы мы ни сделали catch-блоке (в конце концов, исправляя ошибку, мы можем сделать новую ошибку), будет воспринято без возражений.

    Находясь в catch-блоке, мы можем вообще отказаться от каких-либо неотложных мероприятий. С помощью оператора throw; можно повторно возбудить последнее исключение. Этот оператор обязательно должен быть расположен в catch-блоке. В результате повторно запускается всё тот же механизм поиска нового подходящего catch-блока. Стек при этом продолжает разматываться, и если при этом в ходе выполнения программы имела место ситуация "вложенных" контролируемых блоков (из try-блока одной функции прямо или косвенно была вызвана функция, содержащая собственный контролируемый блок), то повторно возбуждённое исключение может быть перехвачено уровнем ниже. Таким образом, можно поручить перехват исключения функции, которая была вызвана ранее и, возможно, не несёт ответственности за возникшую исключительную ситуацию. Если соответствующего перехватчика исключения не окажется, выполнение программы будет остановлено.

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

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

    Эту функцию невозможно переопределить, а из-за жёстких ограничений на её список параметров (он непременно должен быть пустым), нельзя определить соответствующие совместно используемые функции. Функция unexpected - "вещь в себе", заглушка. Известно лишь, что она вызывает функцию terminate, но может вызвать и ещё какую-либо другую функцию. Изменить ситуацию на этом "последнем рубеже" можно лишь одним единственным способом - определив собственную функцию, которая должна заместить функцию unexpected в результате выполнения уже известной функции set_unexpected. Здесь ещё существует возможность исправить положение. Дальше такой возможности уже не будет.

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

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




    Содержание  Назад  Вперед