На этом шаге мы рассмотрим пример использования исключений при работе с потоками.
Следующий пример иллюстрирует использование исключений при работе с потоками. Функция 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>.
Со следующего шага мы начнем рассматривать стандартные функции ввода-вывода.