Шрифт:
Интервал:
Закладка:
template <typename T>
ostream& operator<<(ostream &os, const scientific_type<T> &w) {
format_guard _;
os << scientific << uppercase << showpos;
return os << w.value;
}
5. В функции main сначала поработаем с классом format_guard. Откроем новую область видимости, получим экземпляр класса, а затем применим некоторые флаги форматирования к потоку вывода std::cout:
int main()
{
{
format_guard _;
cout << hex << scientific << showbase << uppercase;
cout << "Numbers with special formatting:n";
cout << 0x123abc << 'n';
cout << 0.123456789 << 'n';
}
6. После того как выведем некоторые числа с помощью флагов форматирования, покинем область видимости. В результате деструктор класса format_guard сбросит настройки форматирования. Чтобы это протестировать, выведем точно такие же числа снова. Они должны выглядеть по-другому:
cout << "Same numbers, but normal formatting again:n";
cout << 0x123abc << 'n';
cout << 0.123456789 << 'n';
7. Теперь воспользуемся классом scientific_type. Выведем на экран три числа с плавающей точкой в ряд. Второе число обернем в класс scientific_type.
Соответственно, оно будет выведено с применением нашего особенного стиля форматирования, а числа, стоящие перед ним и после него, будут иметь форматирование по умолчанию. В то же время мы избежим появления ненужных символов.
cout << "Mixed formatting: "
<< 123.0 << " "
<< scientific_type{123.0} << " "
<< 123.456 << 'n';
}
8. Компиляция и запуск программы дадут следующий результат. Первые два числа будут выведены с конкретным форматированием. Следующие два будут иметь форматирование по умолчанию — это показывает, что наш класс format_guard работает хорошо. Три числа в последних строках также выглядят соответственно нашим ожиданиям. Число, которое стоит посередине, имеет форматирование класса scientific_type, остальные же имеют форматирование по умолчанию:
$ ./pretty_print_on_the_fly
Numbers with special formatting:
0X123ABC
1.234568E-01
Same numbers, but normal formatting again:
1194684
0.123457
Mixed formatting: 123 +1.230000E+02 123.456
Перехватываем читабельные исключения для ошибок потока std::iostream
Ни в одном из примеров, показанных в данной главе, мы не использовали для обнаружения ошибок исключения. Несмотря на такую возможность, можно работать с объектами потоков без исключений — это очень удобно. Если мы попробуем преобразовать десять значений, но где-то в середине будет сгенерирована ошибка, то весь объект потока войдет в ошибочное состояние и перестанет работать. Таким образом, мы не столкнемся с ситуацией, когда преобразуем переменные с ошибочным смещением в потоке. Мы можем выполнять условные преобразования, например, так: if (cin >> foo >> bar >> ...). В случае сбоя этой конструкции мы его обработаем. Использование конструкции try {...} catch... при преобразовании объектов не кажется очень полезным.
Фактически библиотека для работы с потоками ввода/вывода в C++ существовала уже в те времена, когда язык C++ еще не работал с исключениями. Поддержка исключений была добавлена позже, это объясняет отсутствие их первоклассной поддержки в потоковой библиотеке.
Применение исключений для потоковой библиотеки возможно при конфигурации каждого отдельного объекта потока таким образом, чтобы он генерировал исключение в тот момент, когда входит в ошибочное состояние. К сожалению, пояснения к ошибкам, лежащие в объектах исключений, которые мы будем отлавливать, не стандартизированы. Это приводит к появлению не очень информативных сообщений об ошибках, их мы рассмотрим в данном разделе. Если вы действительно хотите использовать исключения для объектов потока, то можете дополнительно опросить библиотеку языка C для состояния ошибок файловой системы, чтобы получить добавочную информацию.
В данном разделе мы напишем программу, которая может генерировать множество разных сбоев, обработаем их с помощью исключений и увидим, как получить более подробное описание этих ошибок.
Как это делается
В этом примере мы реализуем программу, которая открывает файл (тут может быть сгенерирован сбой), а затем считаем оттуда целое число (здесь тоже возможен сбой). Сделаем это с помощью активизированных исключений, а затем увидим, как их обработать.
1. Сначала включим некоторые заголовочные файлы и объявим об использовании пространства имен std:
#include <iostream>
#include <fstream>
#include <system_error>
#include <cstring>
using namespace std;
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';