Урок №187. Функциональный try-блок

  Юрий  | 

  Обновл. 28 Июн 2019  | 

 3836

 ǀ   2 

В этом уроке мы рассмотрим, что такое функциональный try-блок, как его использовать и какие есть нюансы.

Функциональный try-блок

Функционала блоков try и catch вполне достаточно в большинстве случаев, но есть одна конкретная ситуация, в которой их стандартного функционала может не хватать. Рассмотрим следующий код:

В программе выше дочерний класс B вызывает конструктор родительского класса A, который генерирует исключение (при успешном выполнении условия). Поскольку объект b создаётся в блоке try функции main(), то если A выбросит исключение, блок try функции main() словит его и передаст обработчику catch (int).

Следовательно, результат выполнения программы выше:

Oops!

Но что, если нам нужно обрабатывать исключения внутри класса B? Вызов конструктора родительского класса A происходит через список инициализации членов, перед выполнением тела конструктора класса B. Поэтому использовать стандартный блок try здесь не получится.

В этой ситуации мы должны использовать слегка модифицированный блок try — функциональный try-блок (или ещё «функциональный блок try»). Функциональный try-блок используется для установления обработчика исключений вокруг всего тела функции, а не лишь её части (блока кода).

Синтаксис немного сложен для описания, поэтому рассмотрим всё на примере:

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

Construction of A failed
Oops!

Рассмотрим эту программу более подробно.

Во-первых, обратите внимание на добавление ключевого слова try перед списком инициализации членов класса B. Это означает, что всё, что находится после этого ключевого слова (вплоть до конца функции) рассматривается как часть блока try.

Во-вторых, блок catch находится на том же уровне отступа, что и вся функция. Любое исключение, выброшенное между ключевым словом try и концом тела конструктора, будет обработано этим же блоком catch.

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

В программе выше, поскольку мы явно не генерируем исключение внутри блока catch, исключение повторно генерируется и передаётся caller-у на уровень выше, т.е. в функцию main(). Блок catch функции main() ловит и обрабатывает исключение и мы получаем Oops!.

Хотя функциональные try-блоки также могут использоваться и с обычными функциями, которые не являются методами класса, это не распространённая практика, так как случаев, где они могут быть полезны очень и очень мало. Они почти всегда используются только с конструкторами!

Не используйте функциональный try-блок для очистки памяти


Если операция создания объекта неуспешна, то деструктор класса не вызывается. Следовательно, у вас может возникнуть соблазн использовать функциональный try-блок для очистки частично выделенной памяти классом. Однако, обращение к членам объекта, который не создался, считается неопределённым поведением, так как объект «мёртв» ещё до начала выполнения блока catch. Это означает, что вы не можете использовать функциональный try-блок для выполнения очистки памяти класса.

Заключение

Функциональные try-блоки полезны в основном для записывания ошибок в лог-файл перед их передачей на уровень выше в стеке вызовов или для изменения типа выбрасываемого исключения.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (32 оценок, среднее: 4,69 из 5)
Загрузка...

Комментариев: 2

  1. Аватар kmish:

    В статье:

    "Если операция создания объекта неуспешна, то деструктор класса не вызывается. Следовательно, у вас может возникнуть соблазн использовать функциональный try-блок для очистки частично выделенной памяти классом. Однако обращение к членам объекта, который не создался, считается неопределенным поведением, так как объект уже «мертв» до начала выполнения блока catch. Это означает, что вы не можете использовать функциональный try-блок для выполнения очистки памяти класса."

    Еще как работает. Следующий пример демонстрирует это:

    Результат:

    ArrayInt constructor
    Construction of ArrayInt failed
    returnMemory() executed
    Catch (...) executed
    End of main

    Указатель this тащит объект по стеку, значит он не «мертв». Функция returnMemory() возвращает память. Программа работает.

    1. Аватар Cerberus:

      Именно этим и опасно неопределённое поведение: в отличие от явной ошибки, код с UB может работать. А может внезапно перестать работать от любого (подчёркиваю: _любого_, вплоть до флага компиляции) изменения в коде или системе сборки.

Добавить комментарий для kmish Отменить ответ

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