вторник, 21 марта 2023 г.

[prog.c++] Небольшой довесок к теме обеспечения strong exception safety

Не так давно поднял тему обеспечения strong exception safety. Теперь хочу эту тему продолжить маленьким примером.

Представим себе, что есть объект, который должен хранить информацию о неких устройствах. У него есть метод add_device, который нужно вызвать, чтобы добавить информацию о новом устройстве. Этот метод в случае успеха возвращает строковый идентификатор устройства.

Очевидная реализация вышеописанного может выглядеть вот так:

class device_info_manager_t
{
   std::map<compound_device_id_t, device_description_t> m_devices;
   ...
public:
   ...
   [[nodiscard]]
   std::string
   add_device(
      const placement_t & place_id,
      const name_t & name,
      const device_description_t & info);
};

[[nodiscard]]
std::string
device_info_manager_t::add_device(
   const placement_t & place_id,
   const name_t & name,
   const device_description_t & info)
{
   auto [ins_it, was_inserted] = m_devices.emplace(
         compound_device_id_t{place_id, name},
         info);
   if(!was_inserted)
      throw std::runtime_error{"device already added"};

   return it->first.to_string(); // (1)
}

К сожалению, эта реализация метода add_device обеспечивает лишь базовую гарантию, а не строгую.

Это потому, что в точке (1), при создании нового экземпляра std::string может выскочить std::bad_alloc. В этом случае никакой утечки не будет, но в объекте останется информация об устройстве, не смотря на то, что add_device завершился неудачно.

Исправить ситуацию очень просто. Например, вот так:

[[nodiscard]]
std::string
device_info_manager_t::add_device(
   const placement_t & place_id,
   const name_t & name,
   const device_description_t & info)
{
   compound_device_id_t dev_id{place_id, name};
   std::string result{dev_id.to_string()};

   auto [ins_it, was_inserted] = m_devices.emplace(std::move(dev_id), info);
   if(!was_inserted)
      throw std::runtime_error{"device already added"};

   return result;
}

Теперь возвращаемое значение конструируется еще до того, как будет модифицирован сам объект. Так что если возвращаемую строку не получится создать, то мы даже не будем модифицировать объект-менеджер. Если же std::bad_alloc вылетит при вызове emplace, то опять же ничего страшного: объект-менеджер не модифицирован, ранее созданная строка-результат будет просто потеряна.

Так что для описанного случая обеспечить strong exception safety не сложно.

Сложно сразу об этом подумать ;)

PS. Даже если исключения не используются (допустим их в каком-то языке нет или религия не позволяет) проблема все равно остается актуальной и для преждевременных return-ов.

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