C++17 STL Стандартная библиотека шаблонов — страница 76 из 119

};


3. Еще один небольшой вспомогательный класс называется

scientific_type
. Поскольку это шаблон класса, он может оборачивать любой тип, который будет представлять собой переменную-член. По сути, он ничего не делает.


template 

struct scientific_type {

  T value;

  explicit scientific_type(T val) : value{val} {}

};


4. Можно определить собственные настройки форматирования для любого типа, который ранее был обернут в

scientific_type
, поскольку в случае перегрузки оператора потока
>>
потоковая библиотека будет выполнять совершенно другой код при выводе подобных типов. Таким образом, можно выводить научные значения в научном представлении с плавающей точкой, в верхнем регистре и явным префиксом
+
, если они имеют положительные значения. Кроме того, мы используем наш класс
format_guard
, чтобы очистить все настройки при выходе из функции:


template 

ostream& operator<<(ostream &os, const scientific_type&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 

#include 

#include 

#include 


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
, то значит, у нас возникла не ошибка файловой системы.