2. Для использования объектов потока вместе с исключениями нужно их активизировать. Чтобы объект файлового потока генерировал исключение в том случае, если нужный файл не существует или при преобразовании возникли ошибки, следует установить значения некоторых битов, указывающих на сбой, в маске исключения. Если мы затем сделаем нечто вызывающее сбой, это сгенерирует исключение. Активизируя failbit
badbit
, мы включаем генерацию исключений для ошибок файловой системы и преобразования:int main()
{
ifstream f;
f.exceptions(f.failbit | f.badbit);
3. Теперь можно открыть блок try
try {
f.open("non_existant.txt");
int i;
f >> i;
cout << "integer has value: " << i << '\n';
}
4. Если хотя бы в одной из этих ситуаций возникнет ошибка, то будет сгенерирован экземпляр std::ios_base::failure
what()
, которая должна объяснить, что вызвало генерацию исключения. К сожалению, это сообщение не стандартизировано и не слишком информативно. Однако мы можем хотя бы определить, это проблема с errno
существовала с момента создания C++, и она получает значение ошибки, которое мы сейчас можем получить. Функция strerror
преобразует номер ошибки в строку, понятную для человека. Если код равен 0
, то значит, у нас возникла не ошибка файловой системы. catch (ios_base::failure& e) {
cerr << "Caught error: ";
if (errno) {
cerr << strerror(errno) << '\n';
} else {
cerr << e.what() << '\n';
}
}
}
5. Компиляция и запуск программы для двух разных сценариев дадут следующий результат. Если нужно открыть существующий файл, но получить из него целое число нельзя, то мы получим сообщение об ошибке iostream_category
$ ./readable_error_msg
Caught error: ios_base::clear: unspecified iostream_category error
6. Если файл
strerror(errno)
:$ ./readable_error_msg
Caught error: No such file or directory
Как это работает
Мы увидели, как можно добавить исключения для объекта потока s
s.exceptions(s.failbit | s.badbit)
. Таким образом, здесь мы никак не сможем применить для открытия файла, к примеру, конструктор экземпляра std::ifstream
, если хотим получать исключение всякий раз, когда открыть этот файл окажется невозможно:ifstream f {"non_existant.txt"};
f.exceptions(...); // слишком поздно для генерации исключения
И это плохо, поскольку именно благодаря исключениям обработка ошибок получается не столь громоздкой, по сравнению с кодом обработки ошибок для С, который обычно заполнен множеством условий if
Если бы мы попробовали смоделировать разные причины сбоя потоков, то увидели бы, что генерируются одинаковые исключения. Таким образом, можно только понять,
Если любая функция, связанная с системой, встречает ошибочное состояние, то может установить в качестве значения переменной errno
0
(0
означает отсутствие ошибок), а затем вызывающая сторона сможет прочесть этот номер ошибки и определить, что он означает. Единственная проблема заключается в следующем: если наше приложение многопоточно и все потоки используют функции, которые могут устанавливать значение данной переменной, то мы не можем выяснить, 0
, то это не значит, что ошибок нет, — какая-то errno
.