Россия и Беларусь начали и продолжают войну против народа Украины!

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

  Юрий  | 

  |

  Обновл. 15 Сен 2021  | 

 19400

 ǀ   4 

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

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

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

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

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

Oops!

Но что, если нам нужно обрабатывать исключения внутри класса B? Вызов конструктора родительского класса A происходит через список инициализации членов, перед выполнением тела конструктора класса B. Поэтому использовать стандартный блок 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 (130 оценок, среднее: 4,86 из 5)
Загрузка...

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

  1. Alex:

    "Если операция создания объекта неуспешна, то деструктор класса не вызывается". А если перед генерацией исключения были созданы динамические объекты, они будут удалены или нет? Если нет — то как их удалять

    1. Dima:

      Вызвать деструктор напрямую в блоке catch

  2. kmish:

    В статье:

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

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

    Результат:

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

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

    1. Cerberus:

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

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

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