Шаг 477.
Библиотека STL. Ввод-вывод с использованием потоковых классов. Пример использования исключений

    На этом шаге мы рассмотрим пример использования исключений при работе с потоками.

    Следующий пример иллюстрирует использование исключений при работе с потоками. Функция readAndProcessSum читает вещественные числа из потока данных до тех пор, пока не достигнут конец файла. Затем возвращается сумма прочитанных вещественных чисел:

namespace MyLib {
    double readAndProcessSum (std::istream& strm)
    {
        using std::ios;
        double value, sum;

        // Сохранение текущего состояния флагов исключений
        ios::iostate oldExceptions = strm.exceptions();

        // Выдача исключений при установке флагов failbit и badbit
        //  - ВНИМАНИЕ: флаг failbit также устанавливается
        //    при достижении конца файла
        strm.exceptions (ios::failbit | ios::badbit);

        try {
            // Пока поток находится в нормальном состоянии
            //  - прочитать значение и прибавить его к сумме
            sum = 0;
            while (strm >> value) {
                sum += value;
            }
        }
        catch (...) {
            // Если исключение произошло не из-за достижения конца файла,
            // - восстановить старую маску исключений
            // - перезапустить исключение
            if (!strm.eof()) {
                strm.exceptions(oldExceptions);  // Восстановление маски
                throw;                           // Перезапуск исключения
            }
        }

        // Восстановление старой маски исключений
        strm.exceptions (oldExceptions);

        // Возврат суммы
        return sum;
    }
}

    Сначала функция сохраняет состояние маски исключений в переменной oldExceptions, чтобы восстановить ее позднее. Затем поток данных настраивается на выдачу исключений при требуемых условиях. Функция читает значения в цикле и суммирует их до тех пор, пока поток данных остается в нормальном состоянии. При достижении конца файла состояние потока данных перестает быть нормальным, и он генерирует исключение (хотя для флага eofbit исключение не генерировалось). Дело в том, что конец файла обнаруживается при неудачной попытке чтения новых данных, которая также устанавливает флаг failbit. Чтобы избежать выдачи исключения при достижении конца файла, мы организуем локальный перехват исключения и проверяем состояние потока данных функцией eof(). Исключение передается дальше только в том случае, если eof() возвращает false.

    Следует помнить, что восстановление исходной маски исключений также может привести к выдаче исключений. Функция exceptions() генерирует исключение в том случае, если соответствующий флаг уже установлен для потока данных. Поэтому если поток данных генерировал исключения для флага eofbit, failbit или badbit при входе в функцию, эти исключения также будут переданы вызывающей стороне.

    Ниже приведен простой пример вызова этой функции из main().

//---------------------------------------------------------------------------

#include <vcl.h>
#include <iostream>
#include <istream>
#include <cstdlib>

#include <conio.h>   //необходимо для getch()

#pragma hdrstop

//---------------------------------------------------------------------------

#pragma argsused
using namespace std;

std::string ToRus(const std::string &in)
{
  char *buff = new char [in.length()+1];
  CharToOem(in.c_str(),buff);
  std::string out(buff);
  delete [] buff;
  return out;
}

namespace MyLib {
    double readAndProcessSum (std::istream& strm)
    {
        using std::ios;
        double value, sum;

        // Сохранение текущего состояния флагов исключений
        ios::iostate oldExceptions = strm.exceptions();

        // Выдача исключений при установке флагов failbit и badbit
        //  - ВНИМАНИЕ: флаг failbit также устанавливается
        //    при достижении конца файла
        strm.exceptions (ios::failbit | ios::badbit);

        try {
            // Пока поток находится в нормальном состоянии
            //  - прочитать значение и прибавить его к сумме
            sum = 0;
            while (strm >> value) {
                sum += value;
            }
        }
        catch (...) {
            // Если исключение произошло не из-за достижения конца файла,
            // - восстановить старую маску исключений
            // - перезапустить исключение
            if (!strm.eof()) {
                strm.exceptions(oldExceptions);  // Восстановление маски
                throw;                           // Перезапуск исключения
            }
        }

        // Восстановление старой маски исключений
        strm.exceptions (oldExceptions);

        // Возврат суммы
        return sum;
    }
}

int main (int argc, char* argv[])
{
  using namespace std;
  double sum;

  try {
      sum = MyLib::readAndProcessSum(cin);
  }
  catch (const ios::failure& error) {
      cerr << ToRus("Исключение ввода/вывода: ") << error.what() << endl;
      return EXIT_FAILURE;
  }
  catch (const exception& error) {
      cerr << ToRus("Стандартное исключение: ") << error.what() << endl;
      return EXIT_FAILURE;
    }
  catch (...) {
      cerr << ToRus("Неизвестное исключение: ") << endl;
      return EXIT_FAILURE;
  }

  // Вывод суммы
  cout << "sum: " << sum << endl;


  getch();
  return 0;
}

//---------------------------------------------------------------------------
Текст этого примера можно взять здесь.

    Результат работы приложения выглядит так:


Рис.1. Пример работы приложения

    Но возникает вопрос: насколько оправданы эти хлопоты? Проще работать с потоками данных, которые не выдают исключений. В этом случае программа сама генерирует исключение при обнаружении ошибки. Дополнительным достоинством такого решения является возможность работы с пользовательскими сообщениями об ошибках и классами ошибок:

#include <istream>

namespace MyLib {
    double readAndProcessSum (std::istream& strm)
    {
        double value, sum;
    
        // Пока поток остается в нормальном состоянии
        // - прочитать очередное значение и прибавить его к сумме
        sum = 0;
        while (strm >> value) {
            sum += value;
        }
    
        if (!strm.eof()) {
            throw std::ios::failure
                    ("input error in readAndProcessSum()");
        }
    
        // Возврат суммы
        return sum;
    }
}

    Выглядит гораздо проще, не правда ли? Этой версии функции нужен заголовок <string>, поскольку конструктор класса failure получает в аргументе ссылку на константный объект string. Для конструирования объекта этого типа потребуется определение, а для передачи объявления достаточно заголовка <istream>.

    Со следующего шага мы начнем рассматривать стандартные функции ввода-вывода.




Предыдущий шаг Содержание Следующий шаг