>> ws;
getline(is, c.name);
is >> c.population >> c.latitude >> c.longitude;
return is;
}
Мы считываем множество разных элементов. Что произойдет, если один из них даст сбой, а следующий за ним — нет? Означает ли это потенциальное считывание всех следующих элементов со «смещением» в потоке токенов? Нет, этого не произойдет. Если хотя бы один элемент потока ввода не сможет быть преобразован, то объект потока ввода входит в ошибочное состояние и отказывается выполнять дальнейшие преобразования. Это значит, что если, например,
c.population
или c.latitude
не могут быть преобразованы, то остальные операнды >>
будут просто отброшены и мы покинем область действия функции оператора с наполовину заполненным объектом.На вызывающей стороне мы получим оповещение об этом при написании конструкции
if (input_stream >> city_object)
. Такое потоковое выражение неявно преобразуется в булево значение, когда используется как условное выражение. Оно возвращает значение false
, если объект потока ввода находится в ошибочном состоянии. Зная это, можно сбросить поток и выполнить подходящие операции.В данном примере мы не писали подобных условий
if
сами, поскольку позволили выполнить десериализацию итератору std::istream_iterator
. Реализация перегруженной версии оператора ++
для этого итератора также выполняет проверку ошибок во время преобразования. При генерации ошибок преобразование будет приостановлено. В этом состоянии проверка будет возвращать значение true
при сравнении с конечным итератором, что заставляет алгоритм copy завершить работу. Таким образом, мы в безопасности. Заполняем контейнеры с применением итераторов std::istream
В предыдущем примере вы узнали, как можно собрать сложные структуры из потока ввода, а затем заполнить списки или векторы полученными элементами.
В этот раз мы усложним задачу, заполняя на основе стандартного потока ввода контейнер
std::map
. Проблема заключается в том, что мы не можем просто заполнить отдельную структуру значениями и отправить ее в линейный контейнер наподобие списка или вектора, поскольку в ассоциативных массивах полезная нагрузка распределяется между ключом и значением. Однако, как вы увидите, само решение задачи отличается незначительно.После изучения этого примера вы сможете легко выполнять сериализацию и десериализацию сложных структур данных.
Как это делается
В этом примере мы определим другую структуру, как делали в прошлом примере, но на сей раз заполним элементами данной структуры ассоциативный массив, что несколько усложняет задачу, поскольку этот контейнер соотносит ключи и значения вместо размещения всех значений в списке.
1. Сначала включим все необходимые заголовочные файлы и объявим об использовании пространства имен
std
:
#include
#include
#include
#include
#include
#include
using namespace std;
2. Мы хотим создать небольшую базу данных интернет-мемов. Предположим, мем имеет название, описание и год, в который он родился или был создан. Сохраним их в контейнере
std::map
, где название выступит в качестве ключа, а другая информация будет помещена в структуру как значение, связанное с ним:
struct meme {
string description;
size_t year;
};
3. Сначала проигнорируем ключ и просто реализуем перегруженную функцию потокового оператора
>>
для структуры meme
. Предположим, что описание мема окружено кавычками, за ним следует год. Это будет выглядеть так: "some description" 2017
. Описание окружено кавычками, так что может содержать пробелы, поскольку мы знаем, что все символы, стоящие между кавычками, принадлежат описанию. После чтения с помощью конструкции is >> quoted(m.description)
кавычки автоматически используются как разделители и впоследствии отбрасываются, что очень удобно. Сразу после этого считываем число, которое представляет собой год:
istream& operator>>(istream &is, meme &m) {
return is >> quoted(m.description) >> m.year;
}
4. О’кей, теперь примем в расчет название мема в качестве ключа ассоциативного массива. Чтобы вставить мем в массив, нужно создать объект типа
std::pair<тип_ключа, тип_значения>
. Типом ключа является string
, а типом значения — meme
. В названии мема также могут находиться пробелы, поэтому мы используем ту же оболочку, которую применяли для описания. p.first
— это название, а p.second
— целая структура meme
, связанная с ним. Данный объект будет передан другой реализации оператора >>
, которую мы только что создали:
istream& operator >>(istream &is,
pair&p) {
return is >> quoted(p.first) >> p.second;
}
5. На этом все. Напишем функцию
main
, в которой будет создаваться ассоциативный массив, и заполним его. Поскольку мы переопределили оператор >>
, итератор istream_iterator
может работать с данным типом непосредственно. Мы позволим ему десериализовать наши объекты типа meme
, полученные из стандартного потока ввода, и используем итератор вставки, чтобы поместить их в ассоциативный массив:
int main()
{
map m;
copy(istream_iterator>{cin},
{},
inserter(m, end(m)));
6. Прежде чем вывести на экран все, что у нас есть, сначала найдем самое длинное название мема в ассоциативном массиве. Для этого воспользуемся алгоритмом
std::accumulate
. Он получит исходное значение 0u
(u расшифровывается как unsigned — «беззнаковый») и пройдет по всем элементам ассоциативного массива, чтобы слить их воедино. С точки зрения алгоритма accumulate
слияние обычно означает сложение. В нашем случае требуется не численная сумма, а размер самой длинной строки. Для получения этого значения предоставим алгоритму вспомогательную функцию max_func
, которая принимает переменную, содержащую текущий максимальный размер строки (она должна быть беззнаковой, поскольку длина строки знака не имеет), и сравнивает ее с длиной названия текущего мема, чтобы взять максимальное значение. Это делается для каждого элемента. Итоговое возвращаемое значение функции accumulate
представляет собой максимальную длину названия мема:
auto max_func ([](size_t old_max,
const auto &b) {
return max(old_max, b.first.length());
});
size_t width {accumulate(begin(m), end(m),
0u, max_func)};
7. Теперь быстро пройдем по ассоциативному массиву и выведем каждый элемент. Чтобы выходные данные смотрелись более «аккуратно», воспользуемся конструкцией
<< left << setw(width)
:
for (const auto &[meme_name, meme_desc] : m) {
const auto &[desc, year] = meme_desc;
cout << left << setw(width) << meme_name
<< " : " << desc
<< ", " << year << '\n';
}
}
8. На этом все. Нам нужна небольшая база данных интернет-мемов, так что заполним текстовый файл некоторыми примерами:
"Doge" "Very Shiba Inu. so dog. much funny. wow." 2013
"Pepe" "Anthropomorphic frog" 2016