суббота, 19 августа 2017 г.

[prog.c++] Небольшое обновление библиотечки cpp_util

Есть у нас маленькая библиотека 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 будет, грубо говоря, три основных шага:

  1. Проверка всех необходимых условий. Если какое-то условие не выполняется, то может быть брошено исключение.
  2. Преаллоцация необходимых ресурсов для выполнения операции. Тут запросто может выскочить исключение, если каких-то ресурсов нет.
  3. Окончательная фиксация изменений.

Достаточно тяжело написать 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.

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