понедельник, 29 апреля 2019 г.

[prog.c++.flame] Посмотрел тут свежий доклад Саттера про более дешевые исключения для C++

Вот этот доклад с ACCU 2019:

Во времена живого G+ я уже постил ссылку на предложение в комитет, в котором эта идея описывалась в первом приближении. Так что, с одной стороны, отрадно, что если раньше в первом предложении любую неудачную аллокации предлагали рассматривать как unrecoverable error, то сейчас хотя бы уже делят проблемы с аллокаций на два класса: не удалось выделить большой кусок памяти (и после этого можно восстановиться) или не удалось выделить небольшой кусочек памяти (и после этого продолжать работу нельзя). Тем не менее, есть три вещи, которые смущают и настораживают:

Во-первых, как по мне, любые ошибки выделения памяти должны приводить к выборосу исключения по умолчанию. И только если пользователь явно выбрал другое поведение, то только тогда можно дергать std::terminate или что-то подобное. Да, большинство программ вряд ли сможет продолжить свою работу при исчерпании памяти. Но в этом случае эти программы все равно упадут, т.к. ошибку выделения памяти не обработают. А вот у тех, кто будет стараться писать код, который способен пережить bad_alloc, не придется трахаться с вызовами try_emplace или new(std::nothrow). Особенно, если код, в котором это придется учитывать, будет шаблонным, предназначенным для повторного использования в разных условиях.

Во-вторых, мне бы не хотелось, чтобы нарушение контракта (в особенности, нарушение предусловий) всегда рассматривалось как unrecoverable error. Я бы предпочел иметь три уровня реакции при работе с контрактами:

  1. контракты не проверяются вообще. Т.е. в debug-сборках проверяются, в release-сборках -- нет;
  2. контракты проверяются в release-сборках и нарушение контракта -- это unrecoverable error. Приложение принудительно завершается через std::terminate;
  3. контракты проверяются в release-сборках и нарушение контракта приводит к выбросу исключения.

ИМХО, в ряде приложений (например, в серверах, которые обслуживают большое количество запросов от разных клиентов, или в сложных GUI-приложениях, особенно с системой плагинов) ошибки несоблюдения контрактов (в частности, предусловий) все равно будут случаться. А крах всего приложения при нарушении какого-то предусловия -- это слишком дорогое решение.

В-третьих, мне нравится идея помечать функции либо как noexcept, либо как throws. Но я не очень понимаю, откуда берется предположение, что в 95% случаев функции окажутся noexcept (если я правильно понял трактовку на некоторых слайдах). Это, вероятно, от кода зависит. Тот код, который доводится в последние годы писать мне, скорее всего, содержит 95% бросающих исключения функций, нежели небросающих. Отсюда мне не очень нравится идея помечать потенциально бросающие исключения посредством try. И не очень понятно, насколько практично будет это в шаблонном коде.

Так что я бы, наверное, предпочел иметь в C++ noexcept блок.

void launch_work_threads( environment_t & env ) override throws {

   m_threads.resize( this->thread_count() );

   try {
      for( std::size_t i = 0u; i != this->thread_count(); ++i ) {
         m_threads[ i ] = this->make_work_thread( env, i );
      }
   }
   catch(...) {
      noexcept {
         this->io_context().stop();

         // Shutdown all started threads.
         forauto & t : m_threads )
            if( t ) {
               t->join();
               t.reset();
            }
            else
               // No more started threads.
               break;
      }
      throw;
   }
}

И чтобы компилятор выдавал предупреждения, если внутри noexcept-блока вызываются функции, помеченные как throws.

Но будем посмотреть, как все будет развиваться. Как по мне, так лучше бы в C++ дешевые исключения завезли вместо модулей (тем более таких сложных и непонятных, как завозят в C++20).

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