# Function-try-block В C++ существует альтернативный синтаксис для определения тела функции, позволяющий навесить на него целиком перехват и обработку исключений ```C++ // Стандартный способ void f() { try { may_throw(); } catch (...) { handle_error(); } } // Альтернативный синтаксис void f() try { may_throw(); } catch (...) { handle_error(); } ``` Во-первых, запись становится короче, с меньшим уровнем вложенности. Во-вторых, эта фича позволяет нам ловить исключения там, где стандартным способом это сделать невозможно — в списке инициализации класса, при инициализации подобъекта базового класса и подобном. ```C++ struct ThrowInCtor { ThrowInCtor() { throw std::runtime_error("err1"); } }; struct TryStruct1 { TryStruct1() try { } catch (const std::exception& e) { // будет поймано исключение из конструктора `c` std::cout << e.what() << "\n"; } ThrowInCtor c; }; struct TryStruct2 { TryStruct2() { try { } catch (const std::exception& e) { // исключение не будет поймано, поскольку тело конструктора // исполняется после инициализации полей std::cout << e.what() << "\n"; } } ThrowInCtor c; }; ``` На [примере](https://godbolt.org/z/6Yf5cE7W4) с `try-block` для конструктора мы сталкиваемся с, на первый взгляд странной, неожиданностью: несмотря на блок `catch`, исключение вылетает в код, вызывающий конструктор. Это логично, ведь если при инициализации полей класса вылетело исключение, мы никак не можем исправить ситуацию и починить объект. Потому можно иногда встретить такие страшные нагромождения ```C++ struct S { S(...) try : a(...), b(...) { try { init(); } catch (const std::exception& e) { log(e); try_repair(); } } catch (const std::exeption& e) { // не получилось починить или неисправимая ошибка в полях log(e); // implicit rethrow } A a; B b; }; ``` Ну хорошо. А как насчет деструкторов? Ведь из деструкторов крайне нежелательно выкидывать исключения, и возможность красиво и просто поставить `catch`, который бы гарантированно перехватил все, весьма недурна. ```C++ struct DctorThrowTry { ~DctorThrowTry() try { throw std::runtime_error("err"); } catch (const std::exception& e) { std::cout << e.what() << "\n"; } }; ``` Выглядит неплохо. Но у нас C++, так что это [не работает](https://godbolt.org/z/vMhb8nWvq)! Кто-то очень доброжелательный решил, что в случае с деструкторами поведение по умолчанию должно быть таким же как и с конструкторами. То есть **`catch` блок деструктора неявно прокидывает исключение дальше**. И привет всем возможным проблемам с исключениями из деструкторов, в том числе нарушению неявного `noexcept(true)`. Однако, в отличие от конструкторов, для деструкторов добавили возможность подавить неявное пробрасывание пойманного исключения. Для этого нужно всего лишь... добавить `return`! ```C++ struct DctorThrowTry { ~DctorThrowTry() try { throw std::runtime_error("err"); } catch (const std::exception& e) { std::cout << e.what() << "\n"; return; // исключение не будет перевыброшено! } }; ``` Удивительно, но таким образом в C++ есть случай, в котором `return` последней командой в void-функции меняет ее поведение. Также нужно добавить, что в catch блоке деструкторов и конструкторов нельзя обращаться к нестатическим полям и методам класса — будет неопределенное поведение. По понятным причинам. В момент входа в `catch` блок они все уже мертвы. ```C++ struct S { A a; B b; S() try { ... } catch (...) { do_something(a); // UB! } ~S() try { ... } catch (...) { do_something(b); // UB! return; } }; // Но при этом bool fun(T1 a, T2 b) try { ... return true; } catch (...) { // важно: этот блок не ловит исключения, возникающие при инициализации a и b do_something(a); // Ok! return false; } ``` **Итого** 1. Для обычных функций и `main()` с помощью альтернативного синтаксиса можно удобно и красиво перехватывать все исключения, которые могли бы вылететь. И поведение по умолчанию — именно перехват. [Дальше не летит.](https://godbolt.org/z/eYGevjeco) 2. Для конструкторов можно ловить исключения из конструкторов полей, обрабатывать их (печатать в лог), но подавить нельзя. Либо кидаете свое новое исключение, либо пойманное неявно будет проброшено дальше. 3. Для деструкторов также будет неявный проброс, но его можно подавить, добавив `return`. Если что, в Rust нет исключений (но есть очень похожие паники). Живите с этим. ### Полезные ссылки 1. https://en.cppreference.com/w/cpp/language/function-try-block 2. https://mariusbancila.ro/blog/2019/03/13/little-known-cpp-function-try-block/