Как следует обрабатывать ошибки при создании классов RAII?

Если часть распределения ресурсов конструктора, например. сбой оболочки сокета RAII, я просто выбрасываю исключение и покончу с этим? Или мне следует использовать способ std::fstream, где вам нужно проверить is_open() после создания объекта?

Первый кажется более соответствующим названию выделения ресурсов при инициализации, но тогда почему стандартная библиотека в основном заставляет вас проверять код ошибки перед использованием объекта?

Я имею в виду пример на справочной странице для basic_fstream (перефразировано, добавлены примечания):

int main() {
  std::string filename = "test.bin";
  std::fstream s(filename);

  if (!s.is_open()) { // should my socket class require the user to do this?
    std::cout << "failed to open " << filename << '\n';
  } else {
    // ... use it
  }
}

person MHebes    schedule 15.09.2020    source источник
comment
Не все типы отказов одинаковы. Неспособность открыть файл часто является законным и ожидаемым путем, поэтому в этом случае вы не захотите создавать исключение.   -  person Galik    schedule 15.09.2020
comment
Это кони для курсов, так как это зависит от серьезности отказа открыть розетку. Если сбой при открытии сокета считается обычным явлением, которое не ставит под угрозу способность вашего приложения продолжать работу, сделайте это, как fstream. В противном случае сгенерируйте исключение. Проблема с подходом fstream возникает, если происходит сбой, и код забывает проверить (например, используя is_open()) перед выполнением действий, предполагающих отсутствие ошибки. Проблема с созданием исключения заключается в том, что оно заставляет вызывающую сторону реагировать на сбой, даже если вызывающая сторона может безопасно ее игнорировать.   -  person Peter    schedule 15.09.2020


Ответы (1)


Рекомендуемый порядок действий – генерировать исключение при любом сбое, возникающем во время построения. Цитата из часто задаваемых вопросов по C++:

Q. Как я могу обработать конструктор, который дает сбой?

А. Сгенерировать исключение.

Конструкторы не имеют возвращаемого типа, поэтому использовать коды возврата невозможно. Таким образом, лучший способ сигнализировать об ошибке конструктора — выдать исключение. Если у вас нет возможности использовать исключения, «наименее плохой» обходной путь — перевести объект в состояние «зомби», установив бит внутреннего состояния, чтобы объект вел себя как мертвый, даже если он технически еще жив.

Если при построении используется RAII, то конструктор несет дополнительную ответственность за очистку всех уже выделенных ресурсов перед генерацией.

В. Как мне обращаться с ресурсами, если мои конструкторы могут генерировать исключения?

О. Каждый член данных внутри вашего объекта должен навести порядок в своем собственном беспорядке.

Если конструктор выдает исключение, деструктор объекта не запускается. Если ваш объект уже сделал что-то, что нужно отменить (например, выделение памяти, открытие файла или блокировку семафора), эти «вещи, которые нужно отменить», должен запомнить элемент данных внутри объекта.

Что касается того, почему конструкторы std::fstream не генерируют исключения и по умолчанию используют резервный вариант (перевод объекта в состояние «зомби»), то это сочетание истории и удобства лучше объяснено в ответах на Почему потоки ввода-вывода C++ STL не являются «дружественными к исключениям»?.

person dxiv    schedule 15.09.2020
comment
В качестве примера стандартной библиотеки, которая делает это таким образом, рассмотрим std::lock_guard. - person Daniel H; 15.09.2020
comment
@DanielH Верно. Некоторые конструкторы контейнеров также могут вызывать исключения, такие как std::bad_alloc. И все эти случаи связаны с той или иной формой RAII. - person dxiv; 15.09.2020