понедельник, 20 марта 2017 г.

[prog.thoughts] Не хватает мне в C++ noexcept-блоков с compile-time проверками

Написал давеча вот такой простой код на C++, с прицелом на обеспечение сильной гарантии exception safety:

void
stats_controller_t::turn_on()
   {
      std::lock_guard< std::mutex > lock{ m_lock };

      if( status_t::off == m_status )
         {
            const auto run_id = m_run_id + 1;

            send_next_message( very_small_timeout(), run_id );

// (1)
            m_status = status_t::on;
            m_run_id = run_id;
         }
   }

Тут в действиях до точки (1) может возникнуть исключение. Но это не страшно, т.к. никаких изменений в объект stats_controller_t внесено не было. А если мы нормально дошли до точки (1), то новое состояние объекта нужно зафиксировать. Что и происходит посредством двух простых операций присваивания.

На первый взгляд может показаться, что все нормально. Модификация состояния тривиальна и каких-то дополнительных телодвижений от разработчика не требуется. Однако, лично я бы предпочел иметь возможность написать что-то вроде:

void
stats_controller_t::turn_on()
   {
      std::lock_guard< std::mutex > lock{ m_lock };

      if( status_t::off == m_status )
         {
            const auto run_id = m_run_id + 1;

            send_next_message( very_small_timeout(), run_id );
// (1)
            noexcept
               {
                  m_status = status_t::on;
                  m_run_id = run_id;
               }
         }
   }

И чтобы компилятор дал бы мне по рукам, если бы в noexcept-блоке я бы написал какую-нибудь операцию, которая не помечена как noexcept. Зачем мне это нужно?

Затем, что код развивается и со временем, внося правки в какой-то кусок кода, разработчик может тупо не знать, под какие именно условия этот кусок кода затачивается. Например, спустя какое-то время какой-то новый разработчик добавит в noexcept-блок еще одно действие:

noexcept
   {
      m_status = status_t::on;
      store_status_change_timepoint();
      m_run_id = run_id;
   }

А это новое действие ба-бах! и начнет выбрасывать исключения. Тем самым послав изначальную сильную гарантию в /dev/null.

Но еще хуже то, что типы объектов могут меняться со временем. Так, сейчас у меня, допустим, m_run_id имеет тип int. А со временем он может быть заменен на какой-то "тяжелый" тип с бросающим исключения конструктором копирования. Т.е. внешне код в stats_controller_t::turn_on() остается таким же самым, но на практике сильная гарантия опять же отправится в /dev/null.

А вот если бы в C++ была возможность записать noexcept-блок и если бы C++ компилятор допускал бы там выполнение только noexcept-операций (т.е. элементарных действий вроде присваниваний или же разрешал вызов только noexcept-функций и методов), то можно было бы непосредственно в коде фиксировать условия, под которые рассчитан конкретный фрагмент кода.


Интересно: только я задумывался на тему таких noexcept-блоков в C++? Или же это еще кому-нибудь интересно? Спрашиваю потому, что есть C++ RG21 и, в принципе, данную идею можно запулить туда. Если она хоть кому-нибудь представляется стоящей.

PS. Старый блог-пост на связанную тему. Тогда я еще не имел представления о том, что именно будет означать noexcept в С++.

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