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

>> 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