Написал давеча вот такой простой код на 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 в С++.
Комментариев нет:
Отправить комментарий