Есть у нас маленькая библиотека cpp_util, которая является небольшой коллекцией всяких мелких полезностей (часть из которых с развитием C++ теряет актуальность, но все-таки). Время от времени мы туда какие-то полезные мелочи добавляем.
Давеча была добавлена вспомогательная функция terminate_if_throws. Эта функция получает и вызывает переданную ей лямбда-функцию. Если лямбда бросает исключение, то автоматически вызывается std::terminate (поскольку сама terminate_if_throws помечена как noexcept).
Предназначена terminate_if_throws для того, чтобы в явном виде отмечать в коде куски, которые не должны бросать исключений в принципе. А если бросают, то нам не остается ничего другого, как прервать работу приложения, т.к. восстановиться мы уже не можем, а в каком состоянии остались наши данные -- не знаем.
Ну, например, допустим у нас есть какой-то архисложный класс some_complex_data и у него есть метод do_some_modification, который меняет состояние объекта. Мы хотим, чтобы do_some_modification обеспечивал строгую гарантию безопасности исключений: т.е. либо все изменения происходят успешно, либо нет никаких изменений.
Для этого в реализации do_some_modification будет, грубо говоря, три основных шага:
- Проверка всех необходимых условий. Если какое-то условие не выполняется, то может быть брошено исключение.
- Преаллоцация необходимых ресурсов для выполнения операции. Тут запросто может выскочить исключение, если каких-то ресурсов нет.
- Окончательная фиксация изменений.
Достаточно тяжело написать do_some_modification() так, чтобы выжить при возникновении исключений на третьем шаге. Гораздо проще, а потому и надежнее, сделать так, чтобы при возникновении исключения на третьем шаге тупо вызывать std::abort()/std::terminate(). Как раз для этого и предназначена terminate_if_throws: она явным образом выставляет в коде метку о том, что вот здесь мы никаких исключений не ждем в принципе, а если исключение таки случится, то пережить это мы не сможем:
#include <cpp_util_3/terminate_if_throws.hpp> ... // We want to provide strong exception guarantee for that method. void some_complex_data::do_some_modification(const params & p) { // Checks all necessary conditions first. // Some exceptions can be thrown here. check_condition_one(p); check_condition_two(p); ... // Preallocate some resources. // Exceptions are expected here. But this is not a problem // because there is no any actual state changes yet. auto r1 = preallocate_resource_one(p); auto r2 = preallocate_resource_two(p); ... // All preparations are done. We don't expect exceptions // in the following block of code. But if some exception is thrown // then we don't know how to repair from it. cpp_util_3::terminate_if_throws( [&] { do_state_change_action_one(...); do_state_change_action_two(...); ... } ); } |
Так же в cpp_util был добавлен макрос CPP_UTIL_3_UNIX. Сейчас он определяется в cpp_util_3/detect_compiler.hpp если определен один из символов: unix, __unix или __unix__.
PS. В cpp_util вряд ли есть что-то уникальное. Наверняка в больших библиотеках, вроде Boost-а или Folly есть соответствующие аналоги. Но смысл cpp_util был как раз в том, чтобы в мелкие проекты не тянуть тяжелые зависимости масштаба Boost-а или Folly.
Комментариев нет:
Отправить комментарий