14.2.2. Перегрузка оператора ввода >>

Обычно первый параметр оператора ввода является ссылкой на поток, из которого осуществляется чтение, а второй параметр — ссылкой на некий неконстантный объект, в который предстоит прочитать данные. Обычно оператор возвращает ссылку на свой поток. Второй параметр не должен быть константным потому, что задачей оператора ввода и является собственно запись данных в этот объект.

Оператор ввода класса Sales_data

В качестве примера напишем оператор ввода для класса Sales_data:

istream &operator>>(istream &is, Sales_data &item) {

 double price; // инициализировать не нужно; читать в price

               // прежде, чем использовать

 is >> item.bookNo >> item.units_sold >> price;

 if (is) // проверить успех ввода данных

  item.revenue = item.units_sold * price;

 else

  item = Sales_data(); // ввод неудачен: вернуть объект в

                       // стандартное состояние

 return is;

}

За исключением оператора if это определение подобно прежней функции read() (см. раздел 7.1.3). Оператор if проверяет, было ли чтение успешно. Если произойдет ошибка ввода-вывода, он вернет объект Sales_data в состояние пустого объекта. Это гарантирует корректность состояния объекта.

Операторы ввода должны учитывать возможность неудачи ввода, а операторы вывода об этом могут не заботиться.

Ошибки во время ввода

В операторе ввода возможны следующие ошибки.

• Операция чтения может потерпеть неудачу из-за наличия в потоке данных неподходящего типа. Например, после чтения переменной-члена bookNo оператор ввода подразумевает, что следующие два элемента будут числовыми данными. Если во вводе окажутся не числовые данные, поток будет недопустим и все последующее попытки чтения из него потерпят неудачу.

• Во время любой из операций чтения может встретиться конец файла или произойти другая ошибка потока ввода.

Чтобы не проверять каждую часть прочитанных данных, можно проверить состояние потока в целом и только потом использовать прочитанные данные

if (is) // проверить успех ввода данных

 item.revenue = item.units_sold * price;

else

 item = Sales_data(); // ввод неудачен: вернуть объект в

                      // стандартное состояние

При сбое любой из операций чтения значение переменной-члена price останется неопределенным. Следовательно, перед ее использованием следует проверить, допустим ли еще поток ввода. Если это так, осуществляется вычисление значения переменной revenue. В случае ошибки ничего страшного не произойдет, поскольку будет возвращен пустой объект класса Sales_data. Для этого объекту item присваивается новый объект класса Sales_data, созданный при помощи стандартного конструктора. После этого присвоения переменная-член bookNo объекта item будет содержать пустую строку, а его переменные члены revenue и units_sold — нулевое значение.

Возвращение объекта в допустимое состояние особенно важно, если объект мог быть частично изменен прежде, чем произошла ошибка. Например, в данном операторе ввода ошибка могла бы произойти уже после успешного чтения в переменную-член bookNo. В результате значения переменных-членов units_sold и revenue останутся неизменными. Таким образом, новое значение bookNo будет связано с данными прежнего объекта.

Оставляя объект в допустимом состоянии, можно в некоторой степени защитить пользователя, который игнорирует возможность ошибки ввода. Объект будет находиться в пригодном для использования состоянии — все его члены окажутся определены. Кроме того, объект не будет вводить в заблуждение — его данные останутся единообразными.

Проектируя оператор ввода, очень важно решить, что делать в случае ошибки и как вновь сделать объект доступным.

Оповещение об ошибке

Некоторые операторы ввода нуждаются в дополнительной проверке данных. Например, оператор ввода мог бы проверить соответствие формату данных, читаемых в переменную bookNo. В таких случаях оператору ввода возможно понадобится установить флаг состояния потока так, чтобы он означал отказ (см. раздел 8.1.2), хотя с технической точки зрения чтение было успешно. Обычно оператор ввода устанавливает только флаг failbit. Флаг eofbit подразумевал бы конец файла, а бит badbit — нарушение потока. Установку этих флагов лучше оставить библиотеке IO.

Упражнения раздела 14.2.2

Упражнение 14.9. Определите оператор ввода для класса Sales_data.

Упражнение 14.10. Опишите поведение оператора ввода класса Sales_data при следующем вводе:

(а) 0-201-99999-9 10 24.95 (b) 10 24.95 0-210-99999-9

Упражнение 14.11. Что не так со следующим оператором ввода класса Sales_data? Что будет при передаче этому оператору данных предыдущего упражнения?

istream& operator>>(istream& in, Sales_data& s) {

 double price;

 in >> s.bookNo >> s.units_sold >> price;

 s.revenue = s.units_sold * price;

 return in;

}

Упражнение 14.12. Определите оператор ввода для класса, использованного в упражнении 7.40 раздела 7.5.1. Обеспечьте обработку оператором ошибок ввода.